diff --git a/Makefile b/Makefile index 0a706803..c2aeb33b 100644 --- a/Makefile +++ b/Makefile @@ -8,43 +8,49 @@ all: venv format check test build .PHONY: format format: venv - .venv/bin/isort src tests tools examples $(FLAGS) - .venv/bin/black -tpy312 -tpy313 -tpy314 src tests tools examples $(FLAGS) + uv run isort src tests tools examples $(FLAGS) + uv run black -tpy312 src tests tools examples $(FLAGS) .PHONY: check check: venv - .venv/bin/pyright --pythonpath .venv/bin/python src tests tools examples + uv run pyright src tests tools examples .PHONY: test test: venv - .venv/bin/pytest $(FLAGS) + uv run pytest $(FLAGS) .PHONY: coverage coverage: venv coverage erase - COVERAGE_PROCESS_START=.coveragerc .venv/bin/coverage run -m pytest $(FLAGS) + COVERAGE_PROCESS_START=.coveragerc uv run coverage run -m pytest $(FLAGS) coverage combine coverage report .PHONY: demo demo: venv - .venv/bin/python -m tools.query $(FLAGS) + uv run python -m tools.query $(FLAGS) .PHONY: compare compare: venv - .venv/bin/python -m tools.query --batch $(FLAGS) + uv run python -m tools.query --batch $(FLAGS) + +.PHONY: eval +eval: venv + rm -f eval.db + uv run python tools/load_json.py --database eval.db tests/testdata/Episode_53_AdrianTchaikovsky_index + uv run python tools/query.py --batch --database eval.db --answer-results tests/testdata/Episode_53_Answer_results.json --search-results tests/testdata/Episode_53_Search_results.json $(FLAGS) .PHONY: mcp mcp: venv - .venv/bin/mcp dev src/typeagent/mcp/server.py + uv run mcp dev src/typeagent/mcp/server.py .PHONY: profile profile: venv - None: import pprint line_width = min(200, shutil.get_terminal_size().columns) - print(pprint.pformat(obj, width=line_width)) + print(prefix + pprint.pformat(obj, width=line_width) + suffix) def format_code(text: str, line_width=None) -> str: """Format a Python literal expression using pprint. NOTE: The text must be a valid Python literal expression (as produced by repr()). + Falls back to plain text formatting if the text is not a valid literal. """ import ast import pprint if line_width is None: line_width = min(200, shutil.get_terminal_size().columns) - return pprint.pformat(ast.literal_eval(text), width=line_width) + try: + return pprint.pformat(ast.literal_eval(text), width=line_width) + except (ValueError, SyntaxError): + # Fall back to simple pprint of the string itself if it's not a valid literal + return pprint.pformat(text, width=line_width) def reindent(text: str) -> str: diff --git a/src/typeagent/knowpro/conversation_base.py b/src/typeagent/knowpro/conversation_base.py index 4b72ac99..8026472a 100644 --- a/src/typeagent/knowpro/conversation_base.py +++ b/src/typeagent/knowpro/conversation_base.py @@ -13,11 +13,13 @@ answer_response_schema, answers, convknowledge, - knowledge_schema as kplib, +) +from . import ( search_query_schema, searchlang, secindex, ) +from . import knowledge_schema as kplib from ..aitools import model_adapters, utils from ..storage.memory import semrefindex from .convsettings import ConversationSettings diff --git a/src/typeagent/knowpro/knowledge.py b/src/typeagent/knowpro/knowledge.py index 0e2a0767..bb889e58 100644 --- a/src/typeagent/knowpro/knowledge.py +++ b/src/typeagent/knowpro/knowledge.py @@ -7,7 +7,8 @@ from typechat import Result, TypeChatLanguageModel -from . import convknowledge, knowledge_schema as kplib +from . import convknowledge +from . import knowledge_schema as kplib from ..aitools import model_adapters from .interfaces import IKnowledgeExtractor diff --git a/src/typeagent/storage/memory/semrefindex.py b/src/typeagent/storage/memory/semrefindex.py index 6c42022d..4b1946f6 100644 --- a/src/typeagent/storage/memory/semrefindex.py +++ b/src/typeagent/storage/memory/semrefindex.py @@ -7,7 +7,9 @@ from typechat import Failure -from ...knowpro import convknowledge, knowledge_schema as kplib, secindex +from ...knowpro import convknowledge +from ...knowpro import knowledge_schema as kplib +from ...knowpro import secindex from ...knowpro.convsettings import ConversationSettings, SemanticRefIndexSettings from ...knowpro.interfaces import ( # Interfaces.; Other imports. IConversation, diff --git a/tests/benchmarks/test_benchmark_vectorbase.py b/tests/benchmarks/test_benchmark_vectorbase.py index f0fbbcb5..7e75108f 100644 --- a/tests/benchmarks/test_benchmark_vectorbase.py +++ b/tests/benchmarks/test_benchmark_vectorbase.py @@ -14,8 +14,11 @@ import pytest from typeagent.aitools.model_adapters import create_test_embedding_model -from typeagent.aitools.vectorbase import ScoredInt, TextEmbeddingIndexSettings, VectorBase - +from typeagent.aitools.vectorbase import ( + ScoredInt, + TextEmbeddingIndexSettings, + VectorBase, +) # -- Helpers ------------------------------------------------------------------ @@ -61,7 +64,9 @@ def test_1k_vectors(self, benchmark, vb_1k: tuple[VectorBase, np.ndarray]) -> No assert len(result) == 10 assert all(isinstance(r, ScoredInt) for r in result) - def test_10k_vectors(self, benchmark, vb_10k: tuple[VectorBase, np.ndarray]) -> None: + def test_10k_vectors( + self, benchmark, vb_10k: tuple[VectorBase, np.ndarray] + ) -> None: vb, query = vb_10k result = benchmark(vb.fuzzy_lookup_embedding, query, max_hits=10, min_score=0.0) assert len(result) == 10 diff --git a/tests/test_knowledge.py b/tests/test_knowledge.py index 822dc48f..0374dfec 100644 --- a/tests/test_knowledge.py +++ b/tests/test_knowledge.py @@ -5,7 +5,8 @@ from typechat import Failure, Result, Success -from typeagent.knowpro import convknowledge, knowledge_schema as kplib +from typeagent.knowpro import convknowledge +from typeagent.knowpro import knowledge_schema as kplib from typeagent.knowpro.knowledge import ( create_knowledge_extractor, extract_knowledge_from_text, diff --git a/tests/test_semrefindex.py b/tests/test_semrefindex.py index 199d602b..d13878ec 100644 --- a/tests/test_semrefindex.py +++ b/tests/test_semrefindex.py @@ -20,7 +20,12 @@ ITermToSemanticRefIndex, Topic, ) -from typeagent.knowpro.knowledge_schema import Action, ConcreteEntity, Facet, KnowledgeResponse +from typeagent.knowpro.knowledge_schema import ( + Action, + ConcreteEntity, + Facet, + KnowledgeResponse, +) from typeagent.storage import SqliteStorageProvider from typeagent.storage.memory import MemoryStorageProvider from typeagent.storage.memory.semrefindex import ( diff --git a/tests/test_utils.py b/tests/test_utils.py index 4c3edbe5..29233cbe 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -56,6 +56,31 @@ def test_format_code_nested(): assert parsed == obj +def test_format_code_non_literal(): + """Test that format_code gracefully handles non-literal expressions. + + Regression test for commit 59be9a5 which broke debug output when format_code() + was called on repr() of objects containing non-literal elements (e.g., AST nodes, + custom class instances). + """ + + # Create a custom class instance (a non-literal object whose repr() can't be + # evaluated with ast.literal_eval) + class CustomClass: + pass + + obj = CustomClass() + non_literal_repr = repr(obj) + # This repr looks like: <__main__.CustomClass object at 0x...> + + # format_code() should handle this gracefully without raising ValueError + result = utils.format_code(non_literal_repr) + assert isinstance(result, str) + assert len(result) > 0 + # The result should contain the non-literal repr (possibly wrapped in quotes) + assert "CustomClass object" in result or "CustomClass" in result + + def test_load_dotenv(really_needs_auth): # Call load_dotenv and check for at least one expected key load_dotenv() diff --git a/tools/query.py b/tools/query.py index c0524459..c7ec7908 100644 --- a/tools/query.py +++ b/tools/query.py @@ -36,13 +36,15 @@ from typeagent.knowpro import ( answer_response_schema, answers, - knowledge_schema as kplib, +) +from typeagent.knowpro import ( query, search, search_query_schema, searchlang, serialization, ) +from typeagent.knowpro import knowledge_schema as kplib from typeagent.knowpro.convsettings import ConversationSettings from typeagent.knowpro.interfaces import ( IConversation, diff --git a/tools/release.py b/tools/release.py index 22a4ea14..ae217208 100755 --- a/tools/release.py +++ b/tools/release.py @@ -450,9 +450,15 @@ def main(): if exit_code != 0: if args.force: - print("Warning: Failed to create PR -- you can create it yourself", file=sys.stderr) + print( + "Warning: Failed to create PR -- you can create it yourself", + file=sys.stderr, + ) else: - print("Error: Failed to create PR -- but you can create it yourself", file=sys.stderr) + print( + "Error: Failed to create PR -- but you can create it yourself", + file=sys.stderr, + ) return 1 if args.dry_run: diff --git a/uv.lock b/uv.lock index 5701a7c7..a48c793a 100644 --- a/uv.lock +++ b/uv.lock @@ -2386,7 +2386,6 @@ version = "0.4.0.dev0" source = { editable = "." } dependencies = [ { name = "azure-identity" }, - { name = "black" }, { name = "colorama" }, { name = "mcp", extra = ["cli"] }, { name = "numpy" }, @@ -2410,6 +2409,7 @@ logfire = [ dev = [ { name = "azure-mgmt-authorization" }, { name = "azure-mgmt-keyvault" }, + { name = "black" }, { name = "coverage" }, { name = "google-api-python-client" }, { name = "google-auth-httplib2" }, @@ -2428,7 +2428,6 @@ dev = [ [package.metadata] requires-dist = [ { name = "azure-identity", specifier = ">=1.22.0" }, - { name = "black", specifier = ">=25.12.0" }, { name = "colorama", specifier = ">=0.4.6" }, { name = "logfire", marker = "extra == 'logfire'", specifier = ">=4.1.0" }, { name = "mcp", extras = ["cli"], specifier = ">=1.12.1" }, @@ -2449,6 +2448,7 @@ provides-extras = ["logfire"] dev = [ { name = "azure-mgmt-authorization", specifier = ">=4.0.0" }, { name = "azure-mgmt-keyvault", specifier = ">=12.1.1" }, + { name = "black", specifier = ">=25.12.0" }, { name = "coverage", extras = ["toml"], specifier = ">=7.9.1" }, { name = "google-api-python-client", specifier = ">=2.184.0" }, { name = "google-auth-httplib2", specifier = ">=0.2.0" },