Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
74097cf
Unreviewed agent output: make chat and embed interfaces provider-agno…
gvanrossum-ms Feb 17, 2026
d59d7b6
Agent step 2 -- unreviewed
gvanrossum-ms Feb 17, 2026
ff733f5
Agent step 3 -- unreviewed -- use Pydantic's model registry
gvanrossum-ms Feb 17, 2026
1015737
Don't hardcode an incomplete table of embedding sizes
gvanrossum-ms Feb 18, 2026
4bd1387
Fix test failures
gvanrossum-ms Feb 18, 2026
60aa403
Rename model_registry -> model_adapters
gvanrossum-ms Feb 18, 2026
067f3b9
Move pydantic-ai to main deps
gvanrossum Feb 24, 2026
17b959f
Remove obsolete create_embedding_model -- wasn't easy
gvanrossum Feb 25, 2026
76621b4
Merge branch 'main' into agnostic
gvanrossum-ms Feb 25, 2026
6f1286f
Fix test_configure_models_returns_correct_types
gvanrossum-ms Feb 25, 2026
83d6f0a
Fall back on Azure for OpenAI models if only Azure key is present
gvanrossum-ms Feb 25, 2026
2659f30
Use embed_documents() instead of embed(input_type=["document"])
gvanrossum-ms Feb 25, 2026
2b8735b
Fix the mcp test. We now do the right thing with azure endpoint env v…
gvanrossum-ms Feb 25, 2026
05183d9
Remove AsyncEmbeddingModel; migrate all tests to PydanticAIEmbeddingM…
gvanrossum-ms Feb 25, 2026
dec2e6f
Move in-function imports to module level in tests/
gvanrossum-ms Feb 25, 2026
909247d
Don't re-export create_typechat_model from convknowledge.py
gvanrossum-ms Feb 25, 2026
8807cc5
Remove redundant tests that Chat/Embedding models subclass their prot…
gvanrossum-ms Feb 25, 2026
68d3082
Avoid type-ignore in favor of isinstance
gvanrossum-ms Feb 25, 2026
3697f89
Remove ModelWrapper, create_typechat_model; use create_chat_model eve…
gvanrossum-ms Feb 25, 2026
087b7a3
Split up *EmbeddingModel into IEmbedder and CachingEmbeddingModel
gvanrossum-ms Feb 26, 2026
43894bd
Remove max_retries everywhere -- this is now under Pydantic control
gvanrossum-ms Feb 26, 2026
910a99b
Remove embedding_size argument everywhere. Handle it internally
gvanrossum-ms Feb 26, 2026
091bd58
Change default embedding back to ada-002 for backwards compatibility
gvanrossum-ms Feb 26, 2026
7c1c527
Add OPENAI_EMBEDDING_MODEL envvar to set the text embedding (e.g. tex…
gvanrossum-ms Feb 26, 2026
400f869
speed optimization:
bmerkle Feb 27, 2026
1cf46b4
parse_azure_endpoint regex missed & separator [?,]
bmerkle Feb 27, 2026
aab0eee
added test for parsing azure endpoint urls
bmerkle Feb 27, 2026
4e9127a
fixed parse_azure_endpoint tests
bmerkle Feb 27, 2026
f6ae405
1st change:
bmerkle Feb 27, 2026
9c7d37a
The original __repr__ used dir(self), which returns all attributes on…
bmerkle Feb 27, 2026
6e6972e
facets_to_merged_facets used str(facet) instead of str(facet.value)
bmerkle Feb 27, 2026
ccb96f2
Remove duplicate __all__ in interfaces_search.py
bmerkle Feb 27, 2026
b4d4a40
when cur_chunk was empty (i.e. at the very start, or right after yiel…
bmerkle Feb 27, 2026
b52910b
format
bmerkle Feb 27, 2026
b7fd4a9
removed bare import coverage at the top level.
bmerkle Feb 27, 2026
0f413fa
dir(self)lists all attributes on the object including inherited methods,
bmerkle Feb 27, 2026
996ebf0
for objects that don't have .ordinal, the code would crash with Attr…
bmerkle Feb 27, 2026
84e90b8
Merge branch 'main' into bugfix-for-0-4-0-release
bmerkle Mar 1, 2026
f0749e1
Clean up import statements in test_utils.py
bmerkle Mar 1, 2026
4611d84
removed invalid , (comma) in URL specification and updated test
bmerkle Mar 1, 2026
77d06be
- fixed implementation to match the corrected half-open interval sema…
bmerkle Mar 1, 2026
3a69313
- used aenumerate in the for loop
bmerkle Mar 1, 2026
70bc27d
Merged all content from test_mcp_server_unit.py into test_mcp_server.py
bmerkle Mar 1, 2026
d4014cf
explained the additiona if guard
bmerkle Mar 1, 2026
c351f16
renamed is_in_range() to contains_range()
bmerkle Mar 1, 2026
5300f09
changed method to clone()
bmerkle Mar 1, 2026
bd0d329
restored commit 582175ce42dd487e779f7da0eee85cb0e2fbe6de
bmerkle Mar 2, 2026
b32e8a2
- each instance gets its own set
bmerkle Mar 2, 2026
c602cf7
removed comments
bmerkle Mar 2, 2026
0bf1904
- small refactoring
bmerkle Mar 3, 2026
9382891
fix for filtered
bmerkle Mar 3, 2026
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
2 changes: 1 addition & 1 deletion src/typeagent/aitools/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def parse_azure_endpoint(
if not azure_endpoint:
raise RuntimeError(f"Environment variable {endpoint_envvar} not found")

m = re.search(r"[?,]api-version=([\d-]+(?:preview)?)", azure_endpoint)
m = re.search(r"[?&]api-version=([\d-]+(?:preview)?)", azure_endpoint)
if not m:
raise RuntimeError(
f"{endpoint_envvar}={azure_endpoint} doesn't contain valid api-version field"
Expand Down
3 changes: 2 additions & 1 deletion src/typeagent/aitools/vectorbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,9 @@ def fuzzy_lookup_embedding_in_subset(
max_hits: int | None = None,
min_score: float | None = None,
) -> list[ScoredInt]:
ordinals_set = set(ordinals_of_subset)
return self.fuzzy_lookup_embedding(
embedding, max_hits, min_score, lambda i: i in ordinals_of_subset
embedding, max_hits, min_score, lambda i: i in ordinals_set
)

async def fuzzy_lookup(
Expand Down
3 changes: 2 additions & 1 deletion src/typeagent/emails/email_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,8 @@ def _merge_chunks(
yield cur_chunk
cur_chunk = new_chunk
else:
cur_chunk += separator
if cur_chunk:
cur_chunk += separator
cur_chunk += new_chunk

if (len(cur_chunk)) > 0:
Expand Down
13 changes: 7 additions & 6 deletions src/typeagent/knowpro/answers.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,12 @@ async def get_enclosing_date_range_for_text_range(
start_timestamp = (await messages.get_item(range.start.message_ordinal)).timestamp
if not start_timestamp:
return None
end_timestamp = (
(await messages.get_item(range.end.message_ordinal)).timestamp
if range.end
else None
)
end_timestamp: str | None = None
if range.end:
end_ordinal = range.end.message_ordinal
if end_ordinal < await messages.size():
end_timestamp = (await messages.get_item(end_ordinal)).timestamp
# else: range extends to the end of the conversation; leave as None.
return DateRange(
start=Datetime.fromisoformat(start_timestamp),
end=Datetime.fromisoformat(end_timestamp) if end_timestamp else None,
Expand Down Expand Up @@ -535,7 +536,7 @@ def facets_to_merged_facets(facets: list[Facet]) -> MergedFacets:
merged_facets: MergedFacets = {}
for facet in facets:
name = facet.name.lower()
value = str(facet).lower()
value = str(facet.value).lower()
merged_facets.setdefault(name, []).append(value)
return merged_facets

Expand Down
26 changes: 15 additions & 11 deletions src/typeagent/knowpro/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,14 @@ def add(self, value: T, score: float, is_exact_match: bool = True) -> None:
)
)
else:
# New related-only match: hit_count stays 0 because
# only exact matches count as direct hits. This matters
# for select_with_hit_count / _matches_with_min_hit_count
# which filter on hit_count to weed out noise.
self.set_match(
Match(
value,
hit_count=1,
hit_count=0,
score=0.0,
related_hit_count=1,
related_score=score,
Expand Down Expand Up @@ -250,9 +254,11 @@ def smooth_match_score[T](match: Match[T]) -> None:


class SemanticRefAccumulator(MatchAccumulator[SemanticRefOrdinal]):
def __init__(self, search_term_matches: set[str] = set()):
def __init__(self, search_term_matches: set[str] | None = None):
super().__init__()
self.search_term_matches = search_term_matches
self.search_term_matches = (
search_term_matches if search_term_matches is not None else set()
)

def add_term_matches(
self,
Expand Down Expand Up @@ -330,8 +336,7 @@ async def group_matches_by_type(
semantic_ref = await semantic_refs.get_item(match.value)
group = groups.get(semantic_ref.knowledge.knowledge_type)
if group is None:
group = SemanticRefAccumulator()
group.search_term_matches = self.search_term_matches
group = SemanticRefAccumulator(self.search_term_matches)
groups[semantic_ref.knowledge.knowledge_type] = group
group.set_match(match)
return groups
Expand Down Expand Up @@ -513,11 +518,10 @@ def add_ranges(self, text_ranges: "list[TextRange] | TextRangeCollection") -> No
for text_range in text_ranges._ranges:
self.add_range(text_range)

def is_in_range(self, inner_range: TextRange) -> bool:
if len(self._ranges) == 0:
return False
i = bisect.bisect_left(self._ranges, inner_range)
for outer_range in self._ranges[i:]:
def contains_range(self, inner_range: TextRange) -> bool:
# Since ranges are sorted by start, once we pass inner_range's start
# no further range can contain it.
for outer_range in self._ranges:
if outer_range.start > inner_range.start:
break
if inner_range in outer_range:
Expand All @@ -544,7 +548,7 @@ def is_range_in_scope(self, inner_range: TextRange) -> bool:
# We have a very simple impl: we don't intersect/union ranges yet.
# Instead, we ensure that the inner range is not rejected by any outer ranges.
for outer_ranges in self.text_ranges:
if not outer_ranges.is_in_range(inner_range):
if not outer_ranges.contains_range(inner_range):
return False
return True

Expand Down
17 changes: 3 additions & 14 deletions src/typeagent/knowpro/interfaces_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
)

__all__ = [
"SearchTerm",
"KnowledgePropertyName",
"PropertySearchTerm",
"SearchSelectExpr",
"SearchTerm",
"SearchTermGroup",
"SearchTermGroupTypes",
"SemanticRefSearchResult",
"WhenFilter",
"SearchSelectExpr",
]


Expand Down Expand Up @@ -142,15 +143,3 @@ class SemanticRefSearchResult:

term_matches: set[str]
semantic_ref_matches: list[ScoredSemanticRefOrdinal]


__all__ = [
"KnowledgePropertyName",
"PropertySearchTerm",
"SearchSelectExpr",
"SearchTerm",
"SearchTermGroup",
"SearchTermGroupTypes",
"SemanticRefSearchResult",
"WhenFilter",
]
14 changes: 9 additions & 5 deletions src/typeagent/knowpro/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
Thread,
)
from .kplib import ConcreteEntity
from .utils import aenumerate

# TODO: Move to compilelib.py
type BooleanOp = Literal["and", "or", "or_max"]
Expand Down Expand Up @@ -101,11 +102,14 @@ async def get_text_range_for_date_range(
messages = conversation.messages
range_start_ordinal: MessageOrdinal = -1
range_end_ordinal = range_start_ordinal
async for message in messages:
if Datetime.fromisoformat(message.timestamp) in date_range:
async for ordinal, message in aenumerate(messages):
if (
message.timestamp
and Datetime.fromisoformat(message.timestamp) in date_range
):
if range_start_ordinal < 0:
range_start_ordinal = message.ordinal
range_end_ordinal = message.ordinal
range_start_ordinal = ordinal
range_end_ordinal = ordinal
else:
if range_start_ordinal >= 0:
# We have a range, so break.
Expand Down Expand Up @@ -696,7 +700,7 @@ class WhereSemanticRefExpr(QueryOpExpr[SemanticRefAccumulator]):

async def eval(self, context: QueryEvalContext) -> SemanticRefAccumulator:
accumulator = await self.source_expr.eval(context)
filtered = SemanticRefAccumulator(accumulator.search_term_matches)
filtered = SemanticRefAccumulator(set(accumulator.search_term_matches))

# Filter matches asynchronously
filtered_matches = []
Expand Down
8 changes: 3 additions & 5 deletions src/typeagent/knowpro/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,9 @@ class SearchOptions:

def __repr__(self):
parts = []
for key in dir(self):
if not key.startswith("_"):
value = getattr(self, key)
if value is not None:
parts.append(f"{key}={value!r}")
for key, value in vars(self).items():
if not key.startswith("_") and value is not None:
parts.append(f"{key}={value!r}")
return f"{self.__class__.__name__}({', '.join(parts)})"


Expand Down
26 changes: 6 additions & 20 deletions src/typeagent/knowpro/searchlang.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,9 @@ class LanguageSearchOptions(SearchOptions):

def __repr__(self):
parts = []
for key in dir(self):
if not key.startswith("_"):
value = getattr(self, key)
if value is not None:
parts.append(f"{key}={value!r}")
for key, value in vars(self).items():
if not key.startswith("_") and value is not None:
parts.append(f"{key}={value!r}")
return f"{self.__class__.__name__}({', '.join(parts)})"


Expand Down Expand Up @@ -371,6 +369,9 @@ def compile_action_term_as_search_terms(
self.compile_entity_terms_as_search_terms(
action_term.additional_entities, action_group
)
# only append the nested or_max wrapper when created one (use_or_max) and it's non-empty.
if use_or_max and action_group.terms:
term_group.terms.append(action_group)
return term_group

def compile_search_terms(
Expand Down Expand Up @@ -609,21 +610,6 @@ def add_entity_name_to_group(
exact_match_value,
)

def add_search_term_to_groupadd_entity_name_to_group(
self,
entity_term: EntityTerm,
property_name: PropertyNames,
term_group: SearchTermGroup,
exact_match_value: bool = False,
) -> None:
if not entity_term.is_name_pronoun:
self.add_property_term_to_group(
property_name.value,
entity_term.name,
term_group,
exact_match_value,
)

def add_property_term_to_group(
self,
property_name: str,
Expand Down
9 changes: 9 additions & 0 deletions src/typeagent/knowpro/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@

"""Utility functions for the knowpro package."""

from collections.abc import AsyncIterable

from .interfaces import MessageOrdinal, TextLocation, TextRange


async def aenumerate[T](aiterable: AsyncIterable[T], start: int = 0):
i = start
async for item in aiterable:
yield i, item
i += 1


def text_range_from_message_chunk(
message_ordinal: MessageOrdinal,
chunk_ordinal: int = 0,
Expand Down
14 changes: 12 additions & 2 deletions src/typeagent/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
import time
from typing import Any

import coverage
try:
import coverage
except ImportError:
coverage = None # type: ignore[assignment]
from dotenv import load_dotenv

from mcp.server.fastmcp import Context, FastMCP
Expand All @@ -18,7 +21,8 @@
import typechat

# Enable coverage.py before local imports (a no-op unless COVERAGE_PROCESS_START is set).
coverage.process_startup()
if coverage is not None:
coverage.process_startup()

from typeagent.aitools import embeddings, utils
from typeagent.knowpro import answers, query, searchlang
Expand Down Expand Up @@ -246,6 +250,12 @@ async def query_conversation(
return QuestionResponse(
success=True, answer=combined_answer.answer or "", time_used=dt
)
case _:
return QuestionResponse(
success=False,
answer=f"Unexpected answer type: {combined_answer.type}",
time_used=dt,
)


# Run the MCP server
Expand Down
Loading
Loading