Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions hindsight-api/hindsight_api/api/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,7 @@ class CreateMentalModelRequest(BaseModel):
model_config = ConfigDict(
json_schema_extra={
"example": {
"id": "team-communication",
"name": "Team Communication Preferences",
"source_query": "How does the team prefer to communicate?",
"tags": ["team"],
Expand All @@ -1143,6 +1144,9 @@ class CreateMentalModelRequest(BaseModel):
}
)

id: str | None = Field(
None, description="Optional custom ID for the mental model (alphanumeric lowercase with hyphens)"
)
name: str = Field(description="Human-readable name for the mental model")
source_query: str = Field(description="The query to run to generate content")
tags: list[str] = Field(default_factory=list, description="Tags for scoped visibility")
Expand Down Expand Up @@ -2386,6 +2390,7 @@ async def api_create_mental_model(
name=body.name,
source_query=body.source_query,
content="Generating content...",
mental_model_id=body.id if body.id else None,
tags=body.tags if body.tags else None,
max_tokens=body.max_tokens,
trigger=body.trigger.model_dump() if body.trigger else None,
Expand Down
39 changes: 39 additions & 0 deletions hindsight-api/tests/test_reflections.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,45 @@ async def test_delete_mental_model(self, memory: MemoryEngine, request_context):
# Cleanup
await memory.delete_bank(bank_id, request_context=request_context)

@pytest.mark.asyncio
async def test_create_mental_model_with_custom_id(self, memory: MemoryEngine, request_context):
"""Test creating a mental model with a custom ID."""
bank_id = f"test-mental-model-custom-id-{uuid.uuid4().hex[:8]}"

# Create the bank first
await memory.get_bank_profile(bank_id=bank_id, request_context=request_context)

# Create a mental model with a custom ID
custom_id = "team-communication-preferences"
mental_model = await memory.create_mental_model(
bank_id=bank_id,
mental_model_id=custom_id,
name="Team Communication Preferences",
source_query="How does the team prefer to communicate?",
content="The team prefers async communication via Slack",
tags=["team", "communication"],
request_context=request_context,
)

# Verify the custom ID was used
assert mental_model["id"] == custom_id
assert mental_model["name"] == "Team Communication Preferences"
assert mental_model["tags"] == ["team", "communication"]

# Verify we can retrieve it with the custom ID
fetched = await memory.get_mental_model(
bank_id=bank_id,
mental_model_id=custom_id,
request_context=request_context,
)

assert fetched is not None
assert fetched["id"] == custom_id
assert fetched["name"] == "Team Communication Preferences"

# Cleanup
await memory.delete_bank(bank_id, request_context=request_context)


class TestObservationsAPI:
"""Test observations API endpoints.
Expand Down
2 changes: 2 additions & 0 deletions hindsight-cli/src/commands/mental_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub fn create(
bank_id: &str,
name: &str,
source_query: &str,
id: Option<&str>,
verbose: bool,
output_format: OutputFormat,
) -> Result<()> {
Expand All @@ -108,6 +109,7 @@ pub fn create(
};

let request = types::CreateMentalModelRequest {
id: id.map(|s| s.to_string()),
name: name.to_string(),
source_query: source_query.to_string(),
max_tokens: 2048,
Expand Down
8 changes: 6 additions & 2 deletions hindsight-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,10 @@ enum MentalModelCommands {

/// Source query to generate the mental model from
source_query: String,

/// Optional custom ID for the mental model (alphanumeric lowercase with hyphens)
#[arg(long)]
id: Option<String>,
},

/// Update a mental model
Expand Down Expand Up @@ -863,8 +867,8 @@ fn run() -> Result<()> {
MentalModelCommands::Get { bank_id, mental_model_id } => {
commands::mental_model::get(&client, &bank_id, &mental_model_id, verbose, output_format)
}
MentalModelCommands::Create { bank_id, name, source_query } => {
commands::mental_model::create(&client, &bank_id, &name, &source_query, verbose, output_format)
MentalModelCommands::Create { bank_id, name, source_query, id } => {
commands::mental_model::create(&client, &bank_id, &name, &source_query, id.as_deref(), verbose, output_format)
}
MentalModelCommands::Update { bank_id, mental_model_id, name } => {
commands::mental_model::update(&client, &bank_id, &mental_model_id, name, verbose, output_format)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ class CreateMentalModelRequest(BaseModel):
"""
Request model for creating a mental model.
""" # noqa: E501
id: Optional[StrictStr] = None
name: StrictStr = Field(description="Human-readable name for the mental model")
source_query: StrictStr = Field(description="The query to run to generate content")
tags: Optional[List[StrictStr]] = Field(default=None, description="Tags for scoped visibility")
max_tokens: Optional[Annotated[int, Field(le=8192, strict=True, ge=256)]] = Field(default=2048, description="Maximum tokens for generated content")
trigger: Optional[MentalModelTrigger] = Field(default=None, description="Trigger settings")
__properties: ClassVar[List[str]] = ["name", "source_query", "tags", "max_tokens", "trigger"]
__properties: ClassVar[List[str]] = ["id", "name", "source_query", "tags", "max_tokens", "trigger"]

model_config = ConfigDict(
populate_by_name=True,
Expand Down Expand Up @@ -77,6 +78,11 @@ def to_dict(self) -> Dict[str, Any]:
# override the default output from pydantic by calling `to_dict()` of trigger
if self.trigger:
_dict['trigger'] = self.trigger.to_dict()
# set to None if id (nullable) is None
# and model_fields_set contains the field
if self.id is None and "id" in self.model_fields_set:
_dict['id'] = None

return _dict

@classmethod
Expand All @@ -89,6 +95,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
return cls.model_validate(obj)

_obj = cls.model_validate({
"id": obj.get("id"),
"name": obj.get("name"),
"source_query": obj.get("source_query"),
"tags": obj.get("tags"),
Expand Down
6 changes: 6 additions & 0 deletions hindsight-clients/typescript/generated/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,12 @@ export type CreateDirectiveRequest = {
* Request model for creating a mental model.
*/
export type CreateMentalModelRequest = {
/**
* Id
*
* Optional custom ID for the mental model (alphanumeric lowercase with hyphens)
*/
id?: string | null;
/**
* Name
*
Expand Down
Loading
Loading