requisite-ai 0.1.0__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 (68) hide show
  1. requisite_ai-0.1.0/LICENSE +21 -0
  2. requisite_ai-0.1.0/PKG-INFO +557 -0
  3. requisite_ai-0.1.0/README.md +519 -0
  4. requisite_ai-0.1.0/pyproject.toml +60 -0
  5. requisite_ai-0.1.0/requisite/__init__.py +164 -0
  6. requisite_ai-0.1.0/requisite/agents/__init__.py +6 -0
  7. requisite_ai-0.1.0/requisite/agents/agent.py +416 -0
  8. requisite_ai-0.1.0/requisite/agents/registry.py +70 -0
  9. requisite_ai-0.1.0/requisite/ai.py +331 -0
  10. requisite_ai-0.1.0/requisite/capabilities/__init__.py +28 -0
  11. requisite_ai-0.1.0/requisite/capabilities/registry.py +223 -0
  12. requisite_ai-0.1.0/requisite/capabilities/resolvers.py +166 -0
  13. requisite_ai-0.1.0/requisite/config/__init__.py +5 -0
  14. requisite_ai-0.1.0/requisite/config/settings.py +166 -0
  15. requisite_ai-0.1.0/requisite/core/__init__.py +1 -0
  16. requisite_ai-0.1.0/requisite/core/exceptions.py +122 -0
  17. requisite_ai-0.1.0/requisite/core/interfaces.py +193 -0
  18. requisite_ai-0.1.0/requisite/memory/__init__.py +24 -0
  19. requisite_ai-0.1.0/requisite/memory/base.py +82 -0
  20. requisite_ai-0.1.0/requisite/memory/factory.py +70 -0
  21. requisite_ai-0.1.0/requisite/memory/in_process.py +63 -0
  22. requisite_ai-0.1.0/requisite/memory/policies.py +202 -0
  23. requisite_ai-0.1.0/requisite/orchestrators/__init__.py +12 -0
  24. requisite_ai-0.1.0/requisite/orchestrators/base.py +92 -0
  25. requisite_ai-0.1.0/requisite/orchestrators/factory.py +101 -0
  26. requisite_ai-0.1.0/requisite/orchestrators/langgraph_orchestrator.py +148 -0
  27. requisite_ai-0.1.0/requisite/orchestrators/native.py +153 -0
  28. requisite_ai-0.1.0/requisite/prompts/__init__.py +19 -0
  29. requisite_ai-0.1.0/requisite/prompts/registry.py +79 -0
  30. requisite_ai-0.1.0/requisite/prompts/template.py +206 -0
  31. requisite_ai-0.1.0/requisite/providers/__init__.py +12 -0
  32. requisite_ai-0.1.0/requisite/providers/anthropic_provider.py +377 -0
  33. requisite_ai-0.1.0/requisite/providers/azure_openai_provider.py +89 -0
  34. requisite_ai-0.1.0/requisite/providers/base.py +185 -0
  35. requisite_ai-0.1.0/requisite/providers/factory.py +173 -0
  36. requisite_ai-0.1.0/requisite/providers/gemini_provider.py +374 -0
  37. requisite_ai-0.1.0/requisite/providers/groq_provider.py +66 -0
  38. requisite_ai-0.1.0/requisite/providers/openai_provider.py +316 -0
  39. requisite_ai-0.1.0/requisite/py.typed +0 -0
  40. requisite_ai-0.1.0/requisite/skills/__init__.py +6 -0
  41. requisite_ai-0.1.0/requisite/skills/base.py +81 -0
  42. requisite_ai-0.1.0/requisite/skills/registry.py +68 -0
  43. requisite_ai-0.1.0/requisite/telemetry/__init__.py +11 -0
  44. requisite_ai-0.1.0/requisite/telemetry/logging.py +123 -0
  45. requisite_ai-0.1.0/requisite/tools/__init__.py +7 -0
  46. requisite_ai-0.1.0/requisite/tools/base.py +148 -0
  47. requisite_ai-0.1.0/requisite/tools/decorator.py +77 -0
  48. requisite_ai-0.1.0/requisite/tools/registry.py +117 -0
  49. requisite_ai-0.1.0/requisite/tools/schema.py +107 -0
  50. requisite_ai-0.1.0/requisite/workflows/__init__.py +6 -0
  51. requisite_ai-0.1.0/requisite/workflows/workflow.py +169 -0
  52. requisite_ai-0.1.0/requisite_ai.egg-info/PKG-INFO +557 -0
  53. requisite_ai-0.1.0/requisite_ai.egg-info/SOURCES.txt +66 -0
  54. requisite_ai-0.1.0/requisite_ai.egg-info/dependency_links.txt +1 -0
  55. requisite_ai-0.1.0/requisite_ai.egg-info/requires.txt +33 -0
  56. requisite_ai-0.1.0/requisite_ai.egg-info/top_level.txt +1 -0
  57. requisite_ai-0.1.0/setup.cfg +4 -0
  58. requisite_ai-0.1.0/tests/test_agents.py +219 -0
  59. requisite_ai-0.1.0/tests/test_ai.py +188 -0
  60. requisite_ai-0.1.0/tests/test_capabilities.py +218 -0
  61. requisite_ai-0.1.0/tests/test_conversation_policies.py +262 -0
  62. requisite_ai-0.1.0/tests/test_memory.py +224 -0
  63. requisite_ai-0.1.0/tests/test_prompts.py +124 -0
  64. requisite_ai-0.1.0/tests/test_providers.py +493 -0
  65. requisite_ai-0.1.0/tests/test_settings.py +64 -0
  66. requisite_ai-0.1.0/tests/test_telemetry.py +106 -0
  67. requisite_ai-0.1.0/tests/test_tools.py +113 -0
  68. requisite_ai-0.1.0/tests/test_workflows.py +205 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Requisite Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,557 @@
1
+ Metadata-Version: 2.4
2
+ Name: requisite-ai
3
+ Version: 0.1.0
4
+ Summary: A provider-agnostic, plugin-based framework for building AI applications and agents. Declare what you need -- providers, tools, capabilities -- not which SDK provides it.
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/requisite-ai/requisite-ai
7
+ Project-URL: Repository, https://github.com/requisite-ai/requisite-ai
8
+ Keywords: ai,agents,llm,openai,gemini,agentic,orchestration,mcp
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: pydantic>=2.7
13
+ Requires-Dist: pydantic-settings>=2.3
14
+ Provides-Extra: openai
15
+ Requires-Dist: openai>=1.35; extra == "openai"
16
+ Provides-Extra: gemini
17
+ Requires-Dist: google-genai>=1.0; extra == "gemini"
18
+ Provides-Extra: anthropic
19
+ Requires-Dist: anthropic>=0.116; extra == "anthropic"
20
+ Provides-Extra: groq
21
+ Requires-Dist: openai>=1.35; extra == "groq"
22
+ Provides-Extra: azure-openai
23
+ Requires-Dist: openai>=1.35; extra == "azure-openai"
24
+ Provides-Extra: langgraph
25
+ Requires-Dist: langgraph>=0.2; extra == "langgraph"
26
+ Provides-Extra: all
27
+ Requires-Dist: openai>=1.35; extra == "all"
28
+ Requires-Dist: google-genai>=1.0; extra == "all"
29
+ Requires-Dist: anthropic>=0.116; extra == "all"
30
+ Requires-Dist: langgraph>=0.2; extra == "all"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=8.0; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
34
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
35
+ Requires-Dist: ruff>=0.5; extra == "dev"
36
+ Requires-Dist: mypy>=1.10; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # Requisite
40
+
41
+ [![CI](https://github.com/requisite-ai/requisite-ai/actions/workflows/ci.yml/badge.svg)](https://github.com/requisite-ai/requisite-ai/actions/workflows/ci.yml)
42
+ [![codecov](https://codecov.io/gh/requisite-ai/requisite-ai/branch/main/graph/badge.svg)](https://codecov.io/gh/requisite-ai/requisite-ai)
43
+ [![PyPI](https://img.shields.io/pypi/v/requisite-ai.svg)](https://pypi.org/project/requisite-ai/)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
45
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml)
46
+
47
+ **Declare what your AI application needs — not which SDK provides it.**
48
+
49
+ A provider-agnostic, plugin-based Python framework for building AI
50
+ applications and agents. Swap the LLM provider, the multi-agent execution
51
+ engine, or the implementation behind a capability like `"weather"` or
52
+ `"internet_search"` — all via configuration, never a rewrite.
53
+
54
+ ```python
55
+ from requisite import AI
56
+
57
+ ai = AI() # provider="openai" by default
58
+ ai = AI(provider="anthropic", model="claude-sonnet-4-6") # same API, different provider
59
+ ai = AI(provider="gemini", model="gemini-2.5-flash")
60
+ ai = AI(provider="groq", model="llama-3.3-70b-versatile")
61
+ ```
62
+
63
+ ```python
64
+ from requisite import Agent
65
+
66
+ agent = Agent(name="Assistant", provider="openai")
67
+ agent.requires("weather", "internet_search", "filesystem") # not use_tool(specific_impl)
68
+ agent.run("What's the weather in Tokyo?")
69
+ ```
70
+
71
+ ## Install
72
+
73
+ ```bash
74
+ pip install -e .[all] # every provider + langgraph
75
+ pip install -e .[openai] # OpenAI only
76
+ pip install -e .[anthropic] # Anthropic (Claude) only
77
+ pip install -e .[gemini] # Gemini only
78
+ pip install -e .[groq] # Groq only (uses the openai package -- wire-compatible)
79
+ pip install -e .[azure_openai] # Azure OpenAI only (uses the openai package)
80
+ pip install -e .[langgraph] # native + langgraph orchestration
81
+ ```
82
+
83
+ Or with the plain requirements file: `pip install -r requirements.txt`
84
+
85
+ > **Note on the Gemini SDK:** this framework uses the current, unified
86
+ > `google-genai` package (`from google import genai`). Do not install the
87
+ > deprecated `google-generativeai` package — the two conflict.
88
+ >
89
+ > **Note on Azure OpenAI:** uses Azure's current **v1 GA API** (the plain
90
+ > `openai` client pointed at your endpoint) — no separate SDK, no dated
91
+ > `api-version` string. See [ADR-0002](docs/adr/0002-provider-kwargs-and-memory-integration.md).
92
+
93
+ ## Configuration
94
+
95
+ Copy `.env.example` to `.env` and fill in the key(s) for the provider(s) you use:
96
+
97
+ ```env
98
+ OPENAI_API_KEY=
99
+ ANTHROPIC_API_KEY=
100
+ GEMINI_API_KEY=
101
+ GROQ_API_KEY=
102
+ AZURE_OPENAI_API_KEY=
103
+ AZURE_OPENAI_ENDPOINT=
104
+ DEFAULT_PROVIDER=openai
105
+ MODEL=gpt-4o-mini
106
+ TEMPERATURE=0.2
107
+ ```
108
+
109
+ `Settings` (`requisite/config/settings.py`) reads this automatically — you
110
+ never call `os.environ.get` yourself. `.env.example` also reserves
111
+ placeholders for planned integrations (GitHub, Hugging Face, AWS, Azure
112
+ general-purpose credentials, Pinecone, Weaviate) — see `ROADMAP.md`.
113
+
114
+ ## Usage
115
+
116
+ ### Chat
117
+
118
+ ```python
119
+ from requisite import AI
120
+
121
+ ai = AI()
122
+ print(ai.chat("Explain LangGraph in one sentence."))
123
+ ```
124
+
125
+ ### Supported providers
126
+
127
+ | Provider | `provider=` | Model examples | Notes |
128
+ |---|---|---|---|
129
+ | OpenAI | `"openai"` | `gpt-4o-mini`, `gpt-4o` | |
130
+ | Anthropic | `"anthropic"` | `claude-sonnet-4-6`, `claude-opus-4-8` | Native structured output via `messages.parse` |
131
+ | Gemini | `"gemini"` | `gemini-2.5-flash`, `gemini-2.5-pro` | Uses the unified `google-genai` SDK |
132
+ | Groq | `"groq"` | `llama-3.3-70b-versatile`, `openai/gpt-oss-20b` | OpenAI-wire-compatible; uses the `openai` package |
133
+ | Azure OpenAI | `"azure_openai"` | your deployment name | Requires `azure_endpoint` (or `AZURE_OPENAI_ENDPOINT`); current v1 GA API, no `api-version` needed |
134
+
135
+ Switching between any of these is the `provider=`/`AZURE_OPENAI_ENDPOINT`
136
+ change shown above — no other code changes. See
137
+ [ADR-0002](docs/adr/0002-provider-kwargs-and-memory-integration.md) for
138
+ why Groq and Azure OpenAI are implemented as thin `OpenAIProvider`
139
+ subclasses rather than separate SDKs.
140
+
141
+ ### Structured output
142
+
143
+ ```python
144
+ from pydantic import BaseModel
145
+
146
+ class Person(BaseModel):
147
+ name: str
148
+ age: int
149
+
150
+ person = ai.chat("Extract: John is 30 years old.", response_model=Person)
151
+ print(person.name, person.age) # John 30
152
+ ```
153
+
154
+ ### Tool calling
155
+
156
+ ```python
157
+ from requisite.tools import tool
158
+
159
+ @tool
160
+ def get_weather(city: str) -> str:
161
+ """Get the current weather for a city."""
162
+ return f"Sunny, 22C in {city}"
163
+
164
+ response = ai.chat_response("What's the weather in Paris?", tools=[get_weather])
165
+ if response.has_tool_calls:
166
+ call = response.tool_calls[0]
167
+ result = get_weather.tool.execute(**call.arguments)
168
+ ```
169
+
170
+ That's the low-level view. For most applications, let an `Agent` run the
171
+ full tool-calling loop for you (see below).
172
+
173
+ ### Agents
174
+
175
+ ```python
176
+ from requisite import Agent
177
+ from requisite.tools import tool
178
+
179
+ @tool
180
+ def get_weather(city: str) -> str:
181
+ """Get the current weather for a city."""
182
+ return f"Sunny, 22C in {city}"
183
+
184
+ agent = Agent(name="Weather Agent", provider="openai", tools=[get_weather])
185
+ result = agent.run("What's the weather in Paris?")
186
+ print(result.content) # "It's sunny and 22C in Paris."
187
+ print(result.tool_calls_executed) # ["get_weather"]
188
+ ```
189
+
190
+ `Agent` automatically: offers its tools/skills to the model, executes any
191
+ tool calls the model requests, feeds results back, and repeats (up to
192
+ `max_iterations`) until it has a final answer.
193
+
194
+ ### Multi-agent workflows
195
+
196
+ ```python
197
+ from requisite import Agent, Workflow
198
+
199
+ research = Agent(name="Researcher", provider="openai")
200
+ writer = Agent(name="Writer", provider="openai")
201
+
202
+ workflow = Workflow()
203
+ workflow.add(research)
204
+ workflow.add(writer)
205
+
206
+ result = workflow.run("Research AI trends and write a summary.")
207
+ print(result.content)
208
+ ```
209
+
210
+ Run agents in parallel against the same input instead of as a pipeline:
211
+
212
+ ```python
213
+ workflow.parallel()
214
+ result = workflow.run("What is retrieval-augmented generation?")
215
+ ```
216
+
217
+ Switch the execution engine — the `.add()` / `.run()` API never changes:
218
+
219
+ ```python
220
+ workflow.use_langgraph() # requires: pip install langgraph
221
+ result = workflow.run("Research AI trends and write a summary.")
222
+
223
+ workflow.use_native() # back to the built-in, dependency-free engine
224
+ ```
225
+
226
+ ### Skills
227
+
228
+ A skill is a reusable, higher-level capability (vs. a tool, which is
229
+ typically a single function). Skills expose themselves to the model as
230
+ tools automatically:
231
+
232
+ ```python
233
+ from requisite.skills import BaseSkill
234
+
235
+ class ReadFileSkill(BaseSkill):
236
+ def __init__(self):
237
+ super().__init__(name="read_file", description="Read a text file's contents.")
238
+
239
+ def run(self, path: str) -> str:
240
+ with open(path) as f:
241
+ return f.read()
242
+
243
+ agent = Agent(name="File Agent", provider="openai", skills=[ReadFileSkill()])
244
+ ```
245
+
246
+ ### Capabilities: declare *what*, not *which*
247
+
248
+ `agent.use_tool(specific_impl)` binds an agent to one concrete
249
+ implementation at write-time. `agent.requires("weather")` binds it to a
250
+ *name*, resolved at runtime against whichever implementation is
251
+ currently available — a native tool, an MCP server, a cloud API, or a
252
+ third-party plugin:
253
+
254
+ ```python
255
+ from requisite import Agent
256
+
257
+ agent = Agent(name="Assistant", provider="openai")
258
+ agent.requires("weather", "internet_search", "filesystem")
259
+
260
+ result = agent.run("What's the weather in Tokyo?")
261
+ ```
262
+
263
+ Three capabilities ship out of the box, backed by free/keyless APIs and
264
+ the local filesystem (see `requisite/capabilities/resolvers.py`):
265
+ `"filesystem"`, `"weather"`, `"internet_search"`.
266
+
267
+ Register a better provider for the same capability at a higher priority
268
+ and it takes over automatically — application code never changes:
269
+
270
+ ```python
271
+ from requisite.capabilities import default_registry
272
+
273
+ default_registry.register(
274
+ "weather",
275
+ my_paid_weather_tool,
276
+ provider_name="acme-weather",
277
+ priority=10,
278
+ is_available=lambda: bool(os.environ.get("ACME_API_KEY")),
279
+ )
280
+ # agent.requires("weather") now resolves to "acme-weather" when the key
281
+ # is set, and quietly falls back to the built-in provider otherwise.
282
+ ```
283
+
284
+ This is the same interface + registry pattern used for providers and
285
+ orchestrators, one layer up: `CapabilityRegistry.resolve(name)` picks the
286
+ highest-priority provider whose `is_available()` currently returns
287
+ `True`, raising `CapabilityException` if none are.
288
+
289
+ ### Memory: conversation history across separate calls
290
+
291
+ ```python
292
+ from requisite import Agent
293
+ from requisite.memory import InProcessMemory
294
+
295
+ memory = InProcessMemory()
296
+ agent = Agent(name="Assistant", provider="openai", memory=memory, session_id="user-42")
297
+
298
+ agent.run("My name is Alex.")
299
+ result = agent.run("What's my name?") # remembers "Alex" via `memory`
300
+ print(result.content)
301
+ ```
302
+
303
+ `session_id` is required whenever `memory` is set — there's no implicit
304
+ "current user," so an agent with memory but no session_id fails at
305
+ construction time rather than silently sharing one conversation across
306
+ callers. When memory is configured, `run()`/`arun()` must be called with
307
+ a plain string (the new turn); prior history is loaded from `memory`
308
+ automatically. Only the user's turn and the agent's final answer are
309
+ persisted, not intermediate tool-call round-trips — see
310
+ [ADR-0002](docs/adr/0002-provider-kwargs-and-memory-integration.md) for
311
+ the reasoning.
312
+
313
+ `InProcessMemory` (dict-backed, lost on restart) is the zero-dependency
314
+ default. Implement `requisite.memory.base.BaseMemory` for a persistent
315
+ backend (Redis, SQLite, ...) — see `ROADMAP.md`.
316
+
317
+ ### Conversation policies: keeping long histories bounded
318
+
319
+ A conversation that grows unbounded eventually blows past the model's
320
+ context window (or just gets expensive). `conversation_policy=` trims or
321
+ summarizes history once, before each `run()`/`arun()` call — independent
322
+ of whether `memory` is configured:
323
+
324
+ ```python
325
+ from requisite import Agent
326
+ from requisite.memory import InProcessMemory, MessageCountPolicy
327
+
328
+ agent = Agent(
329
+ name="Assistant",
330
+ provider="openai",
331
+ memory=InProcessMemory(),
332
+ session_id="user-42",
333
+ conversation_policy=MessageCountPolicy(max_messages=20), # keep the most recent 20
334
+ )
335
+ ```
336
+
337
+ For long-running conversations where you'd rather compress old context
338
+ than drop it, `SummarizingPolicy` collapses older messages into one
339
+ LLM-generated summary, keeping the most recent few verbatim:
340
+
341
+ ```python
342
+ from requisite import AI
343
+ from requisite.memory import SummarizingPolicy
344
+
345
+ # A separate, cheaper AI instance for summarization is a reasonable choice --
346
+ # summarization quality requirements are usually lower than the agent's own task.
347
+ summarizer = AI(provider="groq", model="llama-3.3-70b-versatile")
348
+
349
+ agent = Agent(
350
+ name="Assistant",
351
+ provider="openai",
352
+ memory=InProcessMemory(),
353
+ session_id="user-42",
354
+ conversation_policy=SummarizingPolicy(summarizer, max_messages=20, keep_recent=6),
355
+ )
356
+ ```
357
+
358
+ See [ADR-0003](docs/adr/0003-prompt-templates-structured-logging-conversation-policy.md)
359
+ for why the policy is applied once per call rather than mid-tool-loop,
360
+ and why it doesn't change what gets persisted to `memory`.
361
+
362
+ ### Prompt templates
363
+
364
+ ```python
365
+ from requisite.prompts import ChatPromptTemplate
366
+
367
+ chat_template = ChatPromptTemplate.from_messages([
368
+ ("system", "You are a {persona}."),
369
+ ("user", "{question}"),
370
+ ])
371
+
372
+ messages = chat_template.format_messages(persona="pirate", question="Where's the treasure?")
373
+ print(ai.chat(messages))
374
+ ```
375
+
376
+ `ChatPromptTemplate.format_messages(...)` returns a plain `list[Message]`
377
+ — pass it to `ai.chat(...)` or `agent.run(...)` exactly like any other
378
+ message sequence. For a single string instead of a full conversation,
379
+ use `PromptTemplate`:
380
+
381
+ ```python
382
+ from requisite.prompts import PromptTemplate
383
+
384
+ translate = PromptTemplate.from_template("Translate to {language}: {text}")
385
+ print(ai.chat(translate.format(language="French", text="Good morning")))
386
+
387
+ # Pre-fill some variables now, leave the rest for later:
388
+ french_translator = translate.partial(language="French")
389
+ print(ai.chat(french_translator.format(text="Good night")))
390
+ ```
391
+
392
+ Register named templates for reuse across an application with
393
+ `PromptTemplateRegistry`.
394
+
395
+ ### Structured logging
396
+
397
+ Every framework module logs through the standard library
398
+ (`logging.getLogger("requisite.<subpackage>")`). Opt into JSON output
399
+ with one call, wherever your application configures logging — this is
400
+ never done automatically by the framework:
401
+
402
+ ```python
403
+ from requisite.telemetry import configure_logging
404
+
405
+ configure_logging(level="INFO", json_format=True)
406
+ ```
407
+
408
+ ```json
409
+ {"timestamp": "...", "level": "DEBUG", "logger": "requisite.capabilities", "message": "Resolved capability 'weather' -> 'open-meteo'", "capability": "weather", "provider_name": "open-meteo"}
410
+ ```
411
+
412
+ Any `extra={...}` fields passed to a log call are merged into the JSON
413
+ payload automatically — the same log call produces a readable line with
414
+ the default formatter and a structured payload with `json_format=True`.
415
+
416
+ ### Streaming & async
417
+
418
+ ```python
419
+ for token in ai.stream("Write a haiku about distributed systems."):
420
+ print(token, end="")
421
+
422
+ text = await ai.achat("Hello!")
423
+ async for token in ai.astream("Hello, streamed!"):
424
+ print(token, end="")
425
+
426
+ result = await agent.arun("What's the weather in Paris?")
427
+ result = await workflow.arun("Research AI trends.")
428
+ ```
429
+
430
+ ### Conversation history
431
+
432
+ ```python
433
+ from requisite import Message
434
+
435
+ history = [
436
+ Message.user("My name is Alex."),
437
+ Message.assistant("Nice to meet you, Alex!"),
438
+ Message.user("What's my name?"),
439
+ ]
440
+ print(ai.chat(history))
441
+ ```
442
+
443
+ See `examples/` for complete, runnable scripts covering each of the above.
444
+
445
+ ## Architecture
446
+
447
+ ```
448
+ requisite/
449
+ ├── core/ # Provider-agnostic data models (Message, ChatResponse,
450
+ │ # ToolCall, ...) and the AIException hierarchy
451
+ ├── config/ # Settings (pydantic-settings, reads .env)
452
+ ├── providers/ # BaseProvider interface + OpenAI, Anthropic, Gemini, Groq,
453
+ │ # Azure OpenAI + ProviderRegistry (extensible, DI-friendly)
454
+ ├── tools/ # Tool, @tool decorator, ToolRegistry, JSON Schema derivation
455
+ ├── skills/ # BaseSkill, SkillRegistry -- reusable higher-level capabilities
456
+ ├── capabilities/ # CapabilityRegistry -- resolve a named capability (e.g.
457
+ │ # "weather") to whichever implementation is available
458
+ ├── memory/ # BaseMemory + InProcessMemory + MemoryRegistry, plus
459
+ │ # BaseConversationPolicy (MessageCountPolicy, SummarizingPolicy)
460
+ ├── prompts/ # PromptTemplate, ChatPromptTemplate, PromptTemplateRegistry
461
+ ├── telemetry/ # Structured (JSON) logging -- opt-in, never automatic
462
+ ├── agents/ # Agent (tool-calling loop, .requires(), optional memory) + AgentRegistry
463
+ ├── orchestrators/ # BaseOrchestrator interface + native (sequential/parallel)
464
+ │ # and langgraph backends + OrchestratorRegistry
465
+ ├── workflows/ # Workflow -- the small, ergonomic multi-agent facade
466
+ └── ai.py # The `AI` facade -- the class most users touch directly
467
+ ```
468
+
469
+ Every layer follows the same pattern: a small abstract interface
470
+ (`BaseProvider`, `BaseOrchestrator`, ...), one or more concrete
471
+ implementations, and a plain, instantiable registry (not a singleton)
472
+ mapping names to constructors. That's what makes each of the following a
473
+ *configuration* change rather than a *code* change:
474
+
475
+ - `AI(provider="openai")` → `AI(provider="gemini")`
476
+ - `Workflow(orchestrator="native")` → `workflow.use_langgraph()`
477
+ - `agent.use_tool(specific_impl)` → `agent.requires("weather")`
478
+
479
+ See **[`ARCHITECTURE.md`](ARCHITECTURE.md)** for the full dependency
480
+ diagram, request-flow walkthroughs (a chat call, an agent's tool-calling
481
+ loop, capability resolution, a multi-agent workflow), and the design
482
+ decisions behind them. See **[`CONTRIBUTING.md`](CONTRIBUTING.md)** for
483
+ the step-by-step to add a new provider, orchestrator backend, or
484
+ capability resolver.
485
+
486
+ ## Error handling
487
+
488
+ All framework exceptions inherit from `AIException`:
489
+
490
+ ```
491
+ AIException
492
+ ├── ConfigurationException # missing/invalid config, unknown provider/orchestrator name
493
+ ├── ProviderException # provider SDK call failed (wraps the original error)
494
+ ├── ToolException # a tool wasn't found, or raised while executing
495
+ ├── SkillException # a skill wasn't found, or raised while executing
496
+ ├── AgentException # agent execution failed (e.g. max_iterations exceeded)
497
+ ├── CapabilityException # a required capability has no available provider
498
+ ├── PromptException # a prompt template was rendered without a required variable
499
+ └── MCPException # reserved for the upcoming MCP integration
500
+ ```
501
+
502
+ Provider SDK errors are never swallowed — they're wrapped with `provider`
503
+ and `original_error` attached, and re-raised via `raise ... from original_error`
504
+ so the original traceback is preserved.
505
+
506
+ ## Testing
507
+
508
+ ```bash
509
+ pytest
510
+ ```
511
+
512
+ Tests never hit the network: the OpenAI and Gemini SDKs are faked via
513
+ `sys.modules` injection, and the `AI` / `Agent` / `Workflow` facades are
514
+ tested against fully in-memory fake providers.
515
+
516
+ ## Roadmap
517
+
518
+ Implemented: 5 providers (OpenAI, Anthropic, Gemini, Groq, Azure OpenAI),
519
+ structured outputs, tool calling, skills, capability resolution
520
+ (`agent.requires(...)`), memory + conversation policies
521
+ (`Agent(memory=..., conversation_policy=...)`), prompt templates,
522
+ structured logging, agents + registry, multi-agent workflows
523
+ (sequential/parallel, native + langgraph backends).
524
+
525
+ See [`ROADMAP.md`](ROADMAP.md) for the full, per-layer status table
526
+ (providers, orchestration strategies, MCP, memory, RAG, ...) and what's
527
+ explicitly out of scope. See [`FEATURES.md`](FEATURES.md) for the same
528
+ information organized as a line-by-line checklist against the original
529
+ project vision.
530
+
531
+ ## Contributing
532
+
533
+ Contributions are welcome:
534
+
535
+ - [`CONTRIBUTING.md`](CONTRIBUTING.md) — dev setup, running checks, and
536
+ step-by-step guides for adding a provider, orchestrator backend, or
537
+ capability resolver.
538
+ - [`ARCHITECTURE.md`](ARCHITECTURE.md) — how the framework fits together
539
+ and why: the interface + registry pattern, request-flow walkthroughs,
540
+ design decisions.
541
+ - [`docs/adr/`](docs/adr/) — Architecture Decision Records: the formal
542
+ rationale behind core interfaces, extension points, plugin discovery,
543
+ configuration model, and the `requisite-core` vs. optional-integrations
544
+ boundary. Start with [ADR-0001](docs/adr/0001-core-architecture-and-interfaces.md).
545
+ - [`DEVELOPMENT.md`](DEVELOPMENT.md) — coding standards, testing
546
+ philosophy, docstring format, logging/error-handling conventions,
547
+ versioning policy.
548
+ - [`ROADMAP.md`](ROADMAP.md) — what's shipped, planned, or out of scope.
549
+
550
+ Please also read the [Code of Conduct](CODE_OF_CONDUCT.md). Security
551
+ issues should go through [`SECURITY.md`](SECURITY.md), not a public issue.
552
+
553
+ See [`CHANGELOG.md`](CHANGELOG.md) for release history.
554
+
555
+ ## License
556
+
557
+ MIT — see [`LICENSE`](LICENSE).