typeagent-py 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {typeagent_py-0.1.0/typeagent_py.egg-info → typeagent_py-0.1.2}/PKG-INFO +1 -1
  2. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/pyproject.toml +4 -1
  3. {typeagent_py-0.1.0 → typeagent_py-0.1.2/typeagent_py.egg-info}/PKG-INFO +1 -1
  4. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent_py.egg-info/SOURCES.txt +14 -1
  5. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent_py.egg-info/top_level.txt +1 -0
  6. typeagent_py-0.1.2/typechat/__about__.py +7 -0
  7. typeagent_py-0.1.2/typechat/__init__.py +28 -0
  8. typeagent_py-0.1.2/typechat/_internal/__init__.py +2 -0
  9. typeagent_py-0.1.2/typechat/_internal/interactive.py +40 -0
  10. typeagent_py-0.1.2/typechat/_internal/model.py +187 -0
  11. typeagent_py-0.1.2/typechat/_internal/result.py +24 -0
  12. typeagent_py-0.1.2/typechat/_internal/translator.py +128 -0
  13. typeagent_py-0.1.2/typechat/_internal/ts_conversion/__init__.py +40 -0
  14. typeagent_py-0.1.2/typechat/_internal/ts_conversion/python_type_to_ts_nodes.py +450 -0
  15. typeagent_py-0.1.2/typechat/_internal/ts_conversion/ts_node_to_string.py +99 -0
  16. typeagent_py-0.1.2/typechat/_internal/ts_conversion/ts_type_nodes.py +81 -0
  17. typeagent_py-0.1.2/typechat/_internal/validator.py +70 -0
  18. typeagent_py-0.1.2/typechat/py.typed +0 -0
  19. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/LICENSE +0 -0
  20. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/README.md +0 -0
  21. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/setup.cfg +0 -0
  22. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_auth.py +0 -0
  23. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_collections.py +0 -0
  24. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_conversation_metadata.py +0 -0
  25. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_demo.py +0 -0
  26. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_embeddings.py +0 -0
  27. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_interfaces.py +0 -0
  28. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_knowledge.py +0 -0
  29. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_kplib.py +0 -0
  30. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_message_text_index_population.py +0 -0
  31. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_message_text_index_serialization.py +0 -0
  32. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_messageindex.py +0 -0
  33. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_podcasts.py +0 -0
  34. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_property_index_population.py +0 -0
  35. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_propindex.py +0 -0
  36. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_query.py +0 -0
  37. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_related_terms_fast.py +0 -0
  38. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_related_terms_index_population.py +0 -0
  39. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_reltermsindex.py +0 -0
  40. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_searchlib.py +0 -0
  41. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_secindex.py +0 -0
  42. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_secindex_storage_integration.py +0 -0
  43. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_semrefindex.py +0 -0
  44. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_serialization.py +0 -0
  45. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_sqlite_indexes.py +0 -0
  46. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_sqlitestore.py +0 -0
  47. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_storage_providers_unified.py +0 -0
  48. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_timestampindex.py +0 -0
  49. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_utils.py +0 -0
  50. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/test/test_vectorbase.py +0 -0
  51. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/aitools/auth.py +0 -0
  52. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/aitools/embeddings.py +0 -0
  53. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/aitools/utils.py +0 -0
  54. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/aitools/vectorbase.py +0 -0
  55. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/answer_context_schema.py +0 -0
  56. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/answer_response_schema.py +0 -0
  57. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/answers.py +0 -0
  58. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/collections.py +0 -0
  59. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/common.py +0 -0
  60. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/convknowledge.py +0 -0
  61. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/convsettings.py +0 -0
  62. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/convutils.py +0 -0
  63. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/date_time_schema.py +0 -0
  64. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/field_helpers.py +0 -0
  65. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/fuzzyindex.py +0 -0
  66. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/interfaces.py +0 -0
  67. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/knowledge.py +0 -0
  68. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/kplib.py +0 -0
  69. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/query.py +0 -0
  70. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/search.py +0 -0
  71. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/search_query_schema.py +0 -0
  72. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/searchlang.py +0 -0
  73. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/searchlib.py +0 -0
  74. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/secindex.py +0 -0
  75. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/serialization.py +0 -0
  76. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/textlocindex.py +0 -0
  77. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/knowpro/utils.py +0 -0
  78. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/mcp/server.py +0 -0
  79. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/podcasts/podcast.py +0 -0
  80. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/podcasts/podcast_import.py +0 -0
  81. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/__init__.py +0 -0
  82. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/memory/__init__.py +0 -0
  83. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/memory/collections.py +0 -0
  84. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/memory/convthreads.py +0 -0
  85. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/memory/messageindex.py +0 -0
  86. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/memory/propindex.py +0 -0
  87. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/memory/provider.py +0 -0
  88. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/memory/reltermsindex.py +0 -0
  89. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/memory/semrefindex.py +0 -0
  90. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/memory/timestampindex.py +0 -0
  91. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/sqlite/__init__.py +0 -0
  92. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/sqlite/collections.py +0 -0
  93. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/sqlite/messageindex.py +0 -0
  94. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/sqlite/propindex.py +0 -0
  95. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/sqlite/provider.py +0 -0
  96. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/sqlite/reltermsindex.py +0 -0
  97. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/sqlite/schema.py +0 -0
  98. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/sqlite/semrefindex.py +0 -0
  99. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/sqlite/timestampindex.py +0 -0
  100. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent/storage/utils.py +0 -0
  101. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent_py.egg-info/dependency_links.txt +0 -0
  102. {typeagent_py-0.1.0 → typeagent_py-0.1.2}/typeagent_py.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: typeagent-py
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: TypeAgent implements an agentic memory framework.
5
5
  Author: Steven Lucco, Umesh Madan, Guido van Rossum
6
6
  Author-email: Guido van Rossum <gvanrossum@microsoft.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "typeagent-py"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "TypeAgent implements an agentic memory framework."
9
9
  authors = [
10
10
  { name = "Guido van Rossum", email = "gvanrossum@microsoft.com" },
@@ -48,6 +48,9 @@ packages = [
48
48
  "typeagent.storage",
49
49
  "typeagent.storage.memory",
50
50
  "typeagent.storage.sqlite",
51
+ "typechat",
52
+ "typechat._internal",
53
+ "typechat._internal.ts_conversion",
51
54
  ]
52
55
 
53
56
  [tool.pytest.ini_options]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: typeagent-py
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: TypeAgent implements an agentic memory framework.
5
5
  Author: Steven Lucco, Umesh Madan, Guido van Rossum
6
6
  Author-email: Guido van Rossum <gvanrossum@microsoft.com>
@@ -84,4 +84,17 @@ typeagent_py.egg-info/PKG-INFO
84
84
  typeagent_py.egg-info/SOURCES.txt
85
85
  typeagent_py.egg-info/dependency_links.txt
86
86
  typeagent_py.egg-info/requires.txt
87
- typeagent_py.egg-info/top_level.txt
87
+ typeagent_py.egg-info/top_level.txt
88
+ typechat/__about__.py
89
+ typechat/__init__.py
90
+ typechat/py.typed
91
+ typechat/_internal/__init__.py
92
+ typechat/_internal/interactive.py
93
+ typechat/_internal/model.py
94
+ typechat/_internal/result.py
95
+ typechat/_internal/translator.py
96
+ typechat/_internal/validator.py
97
+ typechat/_internal/ts_conversion/__init__.py
98
+ typechat/_internal/ts_conversion/python_type_to_ts_nodes.py
99
+ typechat/_internal/ts_conversion/ts_node_to_string.py
100
+ typechat/_internal/ts_conversion/ts_type_nodes.py
@@ -0,0 +1,7 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ # SPDX-FileCopyrightText: Microsoft Corporation
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+ __version__ = "0.0.2"
@@ -0,0 +1,28 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ # SPDX-FileCopyrightText: Microsoft Corporation
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+
8
+ from typechat._internal.model import PromptSection, TypeChatLanguageModel, create_language_model, create_openai_language_model, create_azure_openai_language_model
9
+ from typechat._internal.result import Failure, Result, Success
10
+ from typechat._internal.translator import TypeChatJsonTranslator
11
+ from typechat._internal.ts_conversion import python_type_to_typescript_schema
12
+ from typechat._internal.validator import TypeChatValidator
13
+ from typechat._internal.interactive import process_requests
14
+
15
+ __all__ = [
16
+ "TypeChatLanguageModel",
17
+ "TypeChatJsonTranslator",
18
+ "TypeChatValidator",
19
+ "Success",
20
+ "Failure",
21
+ "Result",
22
+ "python_type_to_typescript_schema",
23
+ "PromptSection",
24
+ "create_language_model",
25
+ "create_openai_language_model",
26
+ "create_azure_openai_language_model",
27
+ "process_requests",
28
+ ]
@@ -0,0 +1,2 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
@@ -0,0 +1,40 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing import Callable, Awaitable
5
+
6
+ async def process_requests(interactive_prompt: str, input_file_name: str | None, process_request: Callable[[str], Awaitable[None]]):
7
+ """
8
+ A request processor for interactive input or input from a text file. If an input file name is specified,
9
+ the callback function is invoked for each line in file. Otherwise, the callback function is invoked for
10
+ each line of interactive input until the user types "quit" or "exit".
11
+
12
+ Args:
13
+ interactive_prompt: Prompt to present to user.
14
+ input_file_name: Input text file name, if any.
15
+ process_request: Async callback function that is invoked for each interactive input or each line in text file.
16
+ """
17
+ if input_file_name is not None:
18
+ with open(input_file_name, "r") as file:
19
+ lines = filter(str.rstrip, file)
20
+ for line in lines:
21
+ if line.startswith("# "):
22
+ continue
23
+ print(interactive_prompt + line)
24
+ await process_request(line)
25
+ else:
26
+ try:
27
+ # Use readline to enable input editing and history
28
+ import readline # type: ignore
29
+ except ImportError:
30
+ pass
31
+ while True:
32
+ try:
33
+ line = input(interactive_prompt)
34
+ except EOFError:
35
+ print("\n")
36
+ break
37
+ if line.lower().strip() in ("quit", "exit"):
38
+ break
39
+ else:
40
+ await process_request(line)
@@ -0,0 +1,187 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ import asyncio
5
+ from types import TracebackType
6
+ from typing_extensions import AsyncContextManager, Literal, Protocol, Self, TypedDict, cast, override
7
+
8
+ from typechat._internal.result import Failure, Result, Success
9
+
10
+ import httpx
11
+
12
+ class PromptSection(TypedDict):
13
+ """
14
+ Represents a section of an LLM prompt with an associated role. TypeChat uses the "user" role for
15
+ prompts it generates and the "assistant" role for previous LLM responses (which will be part of
16
+ the prompt in repair attempts). TypeChat currently doesn't use the "system" role.
17
+ """
18
+ role: Literal["system", "user", "assistant"]
19
+ content: str
20
+
21
+ class TypeChatLanguageModel(Protocol):
22
+ async def complete(self, prompt: str | list[PromptSection]) -> Result[str]:
23
+ """
24
+ Represents a AI language model that can complete prompts.
25
+
26
+ TypeChat uses an implementation of this protocol to communicate
27
+ with an AI service that can translate natural language requests to JSON
28
+ instances according to a provided schema.
29
+ The `create_language_model` function can create an instance.
30
+ """
31
+ ...
32
+
33
+ _TRANSIENT_ERROR_CODES = [
34
+ 429,
35
+ 500,
36
+ 502,
37
+ 503,
38
+ 504,
39
+ ]
40
+
41
+ class HttpxLanguageModel(TypeChatLanguageModel, AsyncContextManager):
42
+ url: str
43
+ headers: dict[str, str]
44
+ default_params: dict[str, str]
45
+ # Specifies the maximum number of retry attempts.
46
+ max_retry_attempts: int = 3
47
+ # Specifies the delay before retrying in milliseconds.
48
+ retry_pause_seconds: float = 1.0
49
+ # Specifies how long a request should wait in seconds
50
+ # before timing out with a Failure.
51
+ timeout_seconds = 10
52
+ _async_client: httpx.AsyncClient
53
+
54
+ def __init__(self, url: str, headers: dict[str, str], default_params: dict[str, str]):
55
+ super().__init__()
56
+ self.url = url
57
+ self.headers = headers
58
+ self.default_params = default_params
59
+ self._async_client = httpx.AsyncClient()
60
+
61
+ @override
62
+ async def complete(self, prompt: str | list[PromptSection]) -> Success[str] | Failure:
63
+ headers = {
64
+ "Content-Type": "application/json",
65
+ **self.headers,
66
+ }
67
+
68
+ if isinstance(prompt, str):
69
+ prompt = [{"role": "user", "content": prompt}]
70
+
71
+ body = {
72
+ **self.default_params,
73
+ "messages": prompt,
74
+ "temperature": 0.0,
75
+ "n": 1,
76
+ }
77
+ retry_count = 0
78
+ while True:
79
+ try:
80
+ response = await self._async_client.post(
81
+ self.url,
82
+ headers=headers,
83
+ json=body,
84
+ timeout=self.timeout_seconds
85
+ )
86
+ if response.is_success:
87
+ json_result = cast(
88
+ dict[Literal["choices"], list[dict[Literal["message"], PromptSection]]],
89
+ response.json()
90
+ )
91
+ return Success(json_result["choices"][0]["message"]["content"] or "")
92
+
93
+ if response.status_code not in _TRANSIENT_ERROR_CODES or retry_count >= self.max_retry_attempts:
94
+ return Failure(f"REST API error {response.status_code}: {response.reason_phrase}")
95
+ except Exception as e:
96
+ if retry_count >= self.max_retry_attempts:
97
+ return Failure(str(e) or f"{repr(e)} raised from within internal TypeChat language model.")
98
+
99
+ await asyncio.sleep(self.retry_pause_seconds)
100
+ retry_count += 1
101
+
102
+ @override
103
+ async def __aenter__(self) -> Self:
104
+ return self
105
+
106
+ @override
107
+ async def __aexit__(self, __exc_type: type[BaseException] | None, __exc_value: BaseException | None, __traceback: TracebackType | None) -> bool | None:
108
+ await self._async_client.aclose()
109
+
110
+ def __del__(self):
111
+ try:
112
+ asyncio.get_running_loop().create_task(self._async_client.aclose())
113
+ except Exception:
114
+ pass
115
+
116
+ def create_language_model(vals: dict[str, str | None]) -> HttpxLanguageModel:
117
+ """
118
+ Creates a language model encapsulation of an OpenAI or Azure OpenAI REST API endpoint
119
+ chosen by a dictionary of variables (typically just `os.environ`).
120
+
121
+ If an `OPENAI_API_KEY` environment variable exists, an OpenAI model is constructed.
122
+ The `OPENAI_ENDPOINT` and `OPENAI_MODEL` environment variables must also be defined or an error will be raised.
123
+
124
+ If an `AZURE_OPENAI_API_KEY` environment variable exists, an Azure OpenAI model is constructed.
125
+ The `AZURE_OPENAI_ENDPOINT` environment variable must also be defined or an exception will be thrown.
126
+
127
+ If none of these key variables are defined, an exception is thrown.
128
+ @returns An instance of `TypeChatLanguageModel`.
129
+
130
+ Args:
131
+ vals: A dictionary of variables. Typically just `os.environ`.
132
+ """
133
+
134
+ def required_var(name: str) -> str:
135
+ val = vals.get(name, None)
136
+ if val is None:
137
+ raise ValueError(f"Missing environment variable {name}.")
138
+ return val
139
+
140
+ if "OPENAI_API_KEY" in vals:
141
+ api_key = required_var("OPENAI_API_KEY")
142
+ model = required_var("OPENAI_MODEL")
143
+ endpoint = vals.get("OPENAI_ENDPOINT", None) or "https://api.openai.com/v1/chat/completions"
144
+ org = vals.get("OPENAI_ORG", None) or ""
145
+ return create_openai_language_model(api_key, model, endpoint, org)
146
+
147
+ elif "AZURE_OPENAI_API_KEY" in vals:
148
+ api_key=required_var("AZURE_OPENAI_API_KEY")
149
+ endpoint=required_var("AZURE_OPENAI_ENDPOINT")
150
+ return create_azure_openai_language_model(api_key, endpoint)
151
+ else:
152
+ raise ValueError("Missing environment variables for OPENAI_API_KEY or AZURE_OPENAI_API_KEY.")
153
+
154
+ def create_openai_language_model(api_key: str, model: str, endpoint: str = "https://api.openai.com/v1/chat/completions", org: str = "") -> HttpxLanguageModel:
155
+ """
156
+ Creates a language model encapsulation of an OpenAI REST API endpoint.
157
+
158
+ Args:
159
+ api_key: The OpenAI API key.
160
+ model: The OpenAI model name.
161
+ endpoint: The OpenAI REST API endpoint.
162
+ org: The OpenAI organization.
163
+ """
164
+ headers = {
165
+ "Authorization": f"Bearer {api_key}",
166
+ "OpenAI-Organization": org,
167
+ }
168
+ default_params = {
169
+ "model": model,
170
+ }
171
+ return HttpxLanguageModel(url=endpoint, headers=headers, default_params=default_params)
172
+
173
+ def create_azure_openai_language_model(api_key: str, endpoint: str) -> HttpxLanguageModel:
174
+ """
175
+ Creates a language model encapsulation of an Azure OpenAI REST API endpoint.
176
+
177
+ Args:
178
+ api_key: The Azure OpenAI API key.
179
+ endpoint: The Azure OpenAI REST API endpoint.
180
+ """
181
+ headers = {
182
+ # Needed when using managed identity
183
+ "Authorization": f"Bearer {api_key}",
184
+ # Needed when using regular API key
185
+ "api-key": api_key,
186
+ }
187
+ return HttpxLanguageModel(url=endpoint, headers=headers, default_params={})
@@ -0,0 +1,24 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ from dataclasses import dataclass
5
+ from typing_extensions import Generic, TypeAlias, TypeVar
6
+
7
+ T = TypeVar("T", covariant=True)
8
+
9
+ @dataclass
10
+ class Success(Generic[T]):
11
+ "An object representing a successful operation with a result of type `T`."
12
+ value: T
13
+
14
+
15
+ @dataclass
16
+ class Failure:
17
+ "An object representing an operation that failed for the reason given in `message`."
18
+ message: str
19
+
20
+
21
+ """
22
+ An object representing a successful or failed operation of type `T`.
23
+ """
24
+ Result: TypeAlias = Success[T] | Failure
@@ -0,0 +1,128 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing_extensions import Generic, TypeVar
5
+
6
+ import pydantic_core
7
+
8
+ from typechat._internal.model import PromptSection, TypeChatLanguageModel
9
+ from typechat._internal.result import Failure, Result, Success
10
+ from typechat._internal.ts_conversion import python_type_to_typescript_schema
11
+ from typechat._internal.validator import TypeChatValidator
12
+
13
+ T = TypeVar("T", covariant=True)
14
+
15
+ class TypeChatJsonTranslator(Generic[T]):
16
+ """
17
+ Represents an object that can translate natural language requests in JSON objects of the given type.
18
+ """
19
+
20
+ model: TypeChatLanguageModel
21
+ validator: TypeChatValidator[T]
22
+ target_type: type[T]
23
+ type_name: str
24
+ schema_str: str
25
+ _max_repair_attempts = 1
26
+
27
+ def __init__(
28
+ self,
29
+ model: TypeChatLanguageModel,
30
+ validator: TypeChatValidator[T],
31
+ target_type: type[T],
32
+ *, # keyword-only parameters follow
33
+ _raise_on_schema_errors: bool = True,
34
+ ):
35
+ """
36
+ Args:
37
+ model: The associated `TypeChatLanguageModel`.
38
+ validator: The associated `TypeChatValidator[T]`.
39
+ target_type: A runtime type object describing `T` - the expected shape of JSON data.
40
+ """
41
+ super().__init__()
42
+ self.model = model
43
+ self.validator = validator
44
+ self.target_type = target_type
45
+
46
+ conversion_result = python_type_to_typescript_schema(target_type)
47
+
48
+ if _raise_on_schema_errors and conversion_result.errors:
49
+ error_text = "".join(f"\n- {error}" for error in conversion_result.errors)
50
+ raise ValueError(f"Could not convert Python type to TypeScript schema: \n{error_text}")
51
+
52
+ self.type_name = conversion_result.typescript_type_reference
53
+ self.schema_str = conversion_result.typescript_schema_str
54
+
55
+ async def translate(self, input: str, *, prompt_preamble: str | list[PromptSection] | None = None) -> Result[T]:
56
+ """
57
+ Translates a natural language request into an object of type `T`. If the JSON object returned by
58
+ the language model fails to validate, repair attempts will be made up until `_max_repair_attempts`.
59
+ The prompt for the subsequent attempts will include the diagnostics produced for the prior attempt.
60
+ This often helps produce a valid instance.
61
+
62
+ Args:
63
+ input: A natural language request.
64
+ prompt_preamble: An optional string or list of prompt sections to prepend to the generated prompt.\
65
+ If a string is given, it is converted to a single "user" role prompt section.
66
+ """
67
+
68
+ messages: list[PromptSection] = []
69
+
70
+ if prompt_preamble:
71
+ if isinstance(prompt_preamble, str):
72
+ prompt_preamble = [{"role": "user", "content": prompt_preamble}]
73
+ messages.extend(prompt_preamble)
74
+
75
+ messages.append({"role": "user", "content": self._create_request_prompt(input)})
76
+
77
+ num_repairs_attempted = 0
78
+ while True:
79
+ completion_response = await self.model.complete(messages)
80
+ if isinstance(completion_response, Failure):
81
+ return completion_response
82
+
83
+ text_response = completion_response.value
84
+ first_curly = text_response.find("{")
85
+ last_curly = text_response.rfind("}") + 1
86
+ error_message: str
87
+ if 0 <= first_curly < last_curly:
88
+ trimmed_response = text_response[first_curly:last_curly]
89
+ try:
90
+ parsed_response = pydantic_core.from_json(trimmed_response, allow_inf_nan=False, cache_strings=False)
91
+ except ValueError as e:
92
+ error_message = f"Error: {e}\n\nAttempted to parse:\n\n{trimmed_response}"
93
+ else:
94
+ result = self.validator.validate_object(parsed_response)
95
+ if isinstance(result, Success):
96
+ return result
97
+ error_message = result.message
98
+ else:
99
+ error_message = f"Response did not contain any text resembling JSON.\nResponse was\n\n{text_response}"
100
+ if num_repairs_attempted >= self._max_repair_attempts:
101
+ return Failure(error_message)
102
+ num_repairs_attempted += 1
103
+ messages.append({"role": "assistant", "content": text_response})
104
+ messages.append({"role": "user", "content": self._create_repair_prompt(error_message)})
105
+
106
+ def _create_request_prompt(self, intent: str) -> str:
107
+ prompt = f"""
108
+ You are a service that translates user requests into JSON objects of type "{self.type_name}" according to the following TypeScript definitions:
109
+ ```
110
+ {self.schema_str}
111
+ ```
112
+ The following is a user request:
113
+ '''
114
+ {intent}
115
+ '''
116
+ The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:
117
+ """
118
+ return prompt
119
+
120
+ def _create_repair_prompt(self, validation_error: str) -> str:
121
+ prompt = f"""
122
+ The above JSON object is invalid for the following reason:
123
+ '''
124
+ {validation_error}
125
+ '''
126
+ The following is a revised JSON object:
127
+ """
128
+ return prompt
@@ -0,0 +1,40 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ from dataclasses import dataclass
5
+ from typing_extensions import TypeAliasType
6
+
7
+ from typechat._internal.ts_conversion.python_type_to_ts_nodes import python_type_to_typescript_nodes
8
+ from typechat._internal.ts_conversion.ts_node_to_string import ts_declaration_to_str
9
+
10
+ __all__ = [
11
+ "python_type_to_typescript_schema",
12
+ "TypeScriptSchemaConversionResult",
13
+ ]
14
+
15
+ @dataclass
16
+ class TypeScriptSchemaConversionResult:
17
+ typescript_schema_str: str
18
+ """The TypeScript declarations generated from the Python declarations."""
19
+
20
+ typescript_type_reference: str
21
+ """The TypeScript string representation of a given Python type."""
22
+
23
+ errors: list[str]
24
+ """Any errors that occurred during conversion."""
25
+
26
+ def python_type_to_typescript_schema(py_type: type | TypeAliasType) -> TypeScriptSchemaConversionResult:
27
+ """Converts a Python type to a TypeScript schema."""
28
+
29
+ node_conversion_result = python_type_to_typescript_nodes(py_type)
30
+
31
+ decl_strs = map(ts_declaration_to_str, node_conversion_result.type_declarations)
32
+ decl_strs = reversed(list(decl_strs))
33
+
34
+ schema_str = "\n".join(decl_strs)
35
+
36
+ return TypeScriptSchemaConversionResult(
37
+ typescript_schema_str=schema_str,
38
+ typescript_type_reference=py_type.__name__,
39
+ errors=node_conversion_result.errors,
40
+ )