Skip to content

Commit 0cf12b5

Browse files
Aayush KatariaAayush Kataria
authored andcommitted
Fixing builds
1 parent 70d3d4c commit 0cf12b5

15 files changed

Lines changed: 101 additions & 52 deletions

File tree

agent_memory_toolkit/_utils.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,36 @@ def _make_memory(
9696
return memory
9797

9898

99-
def _resolve_embedding_dimensions(val: Optional[int]) -> Optional[int]:
99+
def _resolve_embedding_dimensions(val: Optional[int]) -> int:
100100
"""Resolve embedding dimensions from explicit value or ``AI_FOUNDRY_EMBEDDING_DIMENSIONS`` env var.
101101
102102
Defaults to 1536 (the dimension we ship with for ``text-embedding-3-large``
103103
truncated to 1536, which is the size DiskANN is tuned for in our containers).
104+
105+
Raises :class:`ConfigurationError` if the env var is set but cannot be
106+
parsed as a positive integer.
104107
"""
105108
if val is not None:
106109
return val
107-
raw = os.environ.get("AI_FOUNDRY_EMBEDDING_DIMENSIONS", "1536") or "1536"
108-
parsed = int(raw)
109-
return parsed if parsed else 1536
110+
raw = os.environ.get("AI_FOUNDRY_EMBEDDING_DIMENSIONS")
111+
if raw is None or raw == "":
112+
return 1536
113+
try:
114+
parsed = int(raw)
115+
except (ValueError, TypeError) as exc:
116+
raise ConfigurationError(
117+
message=(
118+
f"Invalid configuration for embedding_dimensions: AI_FOUNDRY_EMBEDDING_DIMENSIONS"
119+
f" must be a positive integer, got {raw!r}"
120+
),
121+
parameter="embedding_dimensions",
122+
) from exc
123+
if parsed <= 0:
124+
raise ConfigurationError(
125+
message=(f"Invalid configuration for embedding_dimensions: must be a positive integer, got {parsed}"),
126+
parameter="embedding_dimensions",
127+
)
128+
return parsed
110129

111130

112131
def _resolve_cosmos_throughput_mode(val: Optional[str]) -> str:

agent_memory_toolkit/aio/processors/durable.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async def process_thread(
3333
thread_id,
3434
len(turns) if turns else 0,
3535
)
36-
return ProcessThreadResult(thread_summary=None, extracted=[], deduplicated_count=0, elapsed_ms=0)
36+
return ProcessThreadResult(thread_summary=None, extracted_counts={}, deduplicated_count=0, elapsed_ms=0)
3737

3838
async def generate_user_summary(
3939
self,

agent_memory_toolkit/aio/processors/inprocess.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,14 @@ async def process_thread(
7575
deduped_count = dedup[key]
7676
break
7777

78-
extracted_list: list[dict[str, Any]]
79-
if isinstance(extracted, list):
80-
extracted_list = extracted
81-
elif isinstance(extracted, dict):
82-
extracted_list = [extracted]
83-
else:
84-
extracted_list = []
78+
extracted_counts: dict[str, int] = (
79+
{k: v for k, v in extracted.items() if isinstance(v, int)} if isinstance(extracted, dict) else {}
80+
)
8581

8682
elapsed_ms = int((time.monotonic() - start) * 1000)
8783
return ProcessThreadResult(
8884
thread_summary=thread_summary if isinstance(thread_summary, dict) else None,
89-
extracted=extracted_list,
85+
extracted_counts=extracted_counts,
9086
deduplicated_count=deduped_count,
9187
elapsed_ms=elapsed_ms,
9288
)

agent_memory_toolkit/processors/base.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,18 @@
1616

1717
@dataclass
1818
class ProcessThreadResult:
19-
"""Outcome of a single ``process_thread`` invocation."""
19+
"""Outcome of a single ``process_thread`` invocation.
20+
21+
``extracted_counts`` carries the per-type extraction telemetry returned by
22+
:meth:`ProcessingPipeline.extract_memories` (e.g.
23+
``{"facts_count": 2, "procedural_count": 1, "episodic_count": 0, "updated_count": 0}``).
24+
The actual extracted memory documents are persisted to Cosmos DB by the
25+
pipeline; query them back via the SDK's ``get_memories`` if you need the
26+
raw docs.
27+
"""
2028

2129
thread_summary: Optional[dict[str, Any]] = None
22-
extracted: list[dict[str, Any]] = field(default_factory=list)
30+
extracted_counts: dict[str, int] = field(default_factory=dict)
2331
deduplicated_count: int = 0
2432
elapsed_ms: int = 0
2533

agent_memory_toolkit/processors/durable.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def process_thread(
3838
thread_id,
3939
len(turns) if turns else 0,
4040
)
41-
return ProcessThreadResult(thread_summary=None, extracted=[], deduplicated_count=0, elapsed_ms=0)
41+
return ProcessThreadResult(thread_summary=None, extracted_counts={}, deduplicated_count=0, elapsed_ms=0)
4242

4343
def generate_user_summary(
4444
self,

agent_memory_toolkit/processors/inprocess.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,14 @@ def process_thread(
7171
deduped_count = dedup[key]
7272
break
7373

74-
extracted_list: list[dict[str, Any]]
75-
if isinstance(extracted, list):
76-
extracted_list = extracted
77-
elif isinstance(extracted, dict):
78-
extracted_list = [extracted]
79-
else:
80-
extracted_list = []
74+
extracted_counts: dict[str, int] = (
75+
{k: v for k, v in extracted.items() if isinstance(v, int)} if isinstance(extracted, dict) else {}
76+
)
8177

8278
elapsed_ms = int((time.monotonic() - start) * 1000)
8379
return ProcessThreadResult(
8480
thread_summary=thread_summary if isinstance(thread_summary, dict) else None,
85-
extracted=extracted_list,
81+
extracted_counts=extracted_counts,
8682
deduplicated_count=deduped_count,
8783
elapsed_ms=elapsed_ms,
8884
)

function_app/orchestrators/extract_memories.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ def ExtractMemoriesOrchestrator(context: df.DurableOrchestrationContext):
6464
def em_ExtractMemories(payload: dict) -> dict:
6565
"""Run the LLM extraction step.
6666
67-
Returns the per-type counts produced by ``pipeline.extract_memories``
68-
(``{"facts": N, "procedural": N, "episodic": N}``-shaped). Salience-based
69-
filtering is delegated to the pipeline since it owns the schema.
67+
Returns the per-type counts produced by ``pipeline.extract_memories``,
68+
shaped like
69+
``{"facts_count": N, "procedural_count": N, "episodic_count": N, "updated_count": N}``.
70+
Salience-based filtering is delegated to the pipeline since it owns the
71+
schema.
7072
7173
The pipeline loads recent turns internally, so we do NOT pre-load them in
7274
a separate activity (which would duplicate the query, waste RUs, and open

function_app/shared/config.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,32 @@ def _parse_threshold(name: str, default: int) -> int:
4949
"""Parse an integer threshold env var.
5050
5151
Returns ``default`` when the env var is unset, empty, or invalid (with a
52-
warning logged on invalid input). Use this for the ``*_EVERY_N`` knobs
53-
where ``"0"`` is a valid explicit-disable value but a missing setting
54-
should fall back to the documented default rather than silently disabling
55-
the orchestrator.
52+
warning logged on invalid input). Negative values are also rejected and
53+
fall back to ``default`` so misconfiguration is explicit instead of
54+
silently disabling the orchestrator. Use this for the ``*_EVERY_N`` knobs
55+
where ``"0"`` is a valid explicit-disable value but anything else
56+
nonsensical should fall back.
5657
"""
5758
raw = os.environ.get(name)
5859
if raw is None or raw == "":
5960
return default
6061
try:
61-
return int(raw)
62+
parsed = int(raw)
6263
except (ValueError, TypeError):
6364
logger.warning(
6465
"Invalid value for %s=%r, using default %d", name, raw, default,
6566
)
6667
return default
68+
if parsed < 0:
69+
logger.warning(
70+
"Negative value for %s=%r is not allowed; using default %d "
71+
"(set to 0 to explicitly disable)",
72+
name,
73+
raw,
74+
default,
75+
)
76+
return default
77+
return parsed
6778

6879

6980
def _parse_int(name: str, default: int) -> int:

tests/integration/test_processor_integration.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,12 @@ def test_flush_invokes_pipeline_with_correct_args(self):
3838
"type": "summary",
3939
"content": "Conversation about Paris.",
4040
}
41-
pipeline.extract_memories.return_value = [
42-
{"id": "fact-1", "type": "fact", "content": "User likes Paris."},
43-
]
41+
pipeline.extract_memories.return_value = {
42+
"facts_count": 1,
43+
"procedural_count": 0,
44+
"episodic_count": 0,
45+
"updated_count": 0,
46+
}
4447
pipeline.deduplicate_facts.return_value = {"deduplicated": 0}
4548

4649
processor = InProcessProcessor(pipeline=pipeline)
@@ -60,9 +63,12 @@ def test_flush_invokes_pipeline_with_correct_args(self):
6063
"type": "summary",
6164
"content": "Conversation about Paris.",
6265
}
63-
assert result.extracted == [
64-
{"id": "fact-1", "type": "fact", "content": "User likes Paris."},
65-
]
66+
assert result.extracted_counts == {
67+
"facts_count": 1,
68+
"procedural_count": 0,
69+
"episodic_count": 0,
70+
"updated_count": 0,
71+
}
6672
client.get_thread.assert_called_once_with(thread_id="thread-paris", user_id="u-paris", memory_type="turn")
6773
pipeline.generate_thread_summary.assert_called_once_with("u-paris", "thread-paris")
6874
pipeline.extract_memories.assert_called_once_with("u-paris", "thread-paris")
@@ -90,7 +96,7 @@ def test_flush_is_a_noop(self, caplog):
9096

9197
assert isinstance(result, ProcessThreadResult)
9298
assert result.thread_summary is None
93-
assert result.extracted == []
99+
assert result.extracted_counts == {}
94100
assert result.deduplicated_count == 0
95101

96102
pipeline.generate_thread_summary.assert_not_called()

tests/integration/test_processor_integration_async.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ async def test_flush_invokes_pipeline_with_correct_args(self):
3434
"type": "summary",
3535
"content": "Conversation about Paris.",
3636
}
37-
pipeline.extract_memories.return_value = [
38-
{"id": "fact-1", "type": "fact", "content": "User likes Paris."},
39-
]
37+
pipeline.extract_memories.return_value = {
38+
"facts_count": 1,
39+
"procedural_count": 0,
40+
"episodic_count": 0,
41+
"updated_count": 0,
42+
}
4043
pipeline.deduplicate_facts.return_value = {"deduplicated": 0}
4144

4245
processor = AsyncInProcessProcessor(pipeline=pipeline)
@@ -56,9 +59,12 @@ async def test_flush_invokes_pipeline_with_correct_args(self):
5659
"type": "summary",
5760
"content": "Conversation about Paris.",
5861
}
59-
assert result.extracted == [
60-
{"id": "fact-1", "type": "fact", "content": "User likes Paris."},
61-
]
62+
assert result.extracted_counts == {
63+
"facts_count": 1,
64+
"procedural_count": 0,
65+
"episodic_count": 0,
66+
"updated_count": 0,
67+
}
6268
client.get_thread.assert_awaited_once_with(thread_id="thread-paris", user_id="u-paris", memory_type="turn")
6369
pipeline.generate_thread_summary.assert_called_once_with("u-paris", "thread-paris")
6470
pipeline.extract_memories.assert_called_once_with("u-paris", "thread-paris")
@@ -85,7 +91,7 @@ async def test_flush_is_a_noop(self, caplog):
8591

8692
assert isinstance(result, ProcessThreadResult)
8793
assert result.thread_summary is None
88-
assert result.extracted == []
94+
assert result.extracted_counts == {}
8995
assert result.deduplicated_count == 0
9096

9197
pipeline.generate_thread_summary.assert_not_called()

0 commit comments

Comments
 (0)