_No description in Cargo.toml._
| Tier | infrastructure |
| Role | unclassified (baselined) |
| Path | crates/infrastructure/ai |
| Edition | 2021 |
| Targets | infrastructure_ai, connection_setup_smoke |
| Public items | 4 across 2 modules |
| Tests | 72 |
What it is for
Library crates for AI provider integration.
Provides a pluggable interface for AI backends with support for multiple providers, request/response types, and a stub provider for testing.
# Features
- AiProvider trait: Pluggable backend interface
- CompletionRequest/Response: Standard request/response types
- ProviderRegistry: Multi-provider management
- StubAiProvider: Testing and development provider
# Cargo Features
http- Enable HTTP-based providers (OpenRouter, Anthropic)postgres- Enable encrypted credential storagefull- Enable all features
# Example
use infrastructure_ai::{AiProvider, CompletionRequest, StubAiProvider};
# async fn example() -> infrastructure_ai::Result<()> {
// Use the stub provider for testing
let provider = StubAiProvider::new(true);
// Create a completion request
let request = CompletionRequest::new(
"You are a helpful assistant.",
"What is 2 + 2?"
)
.with_max_tokens(100)
.with_temperature(0.5);
// Get a response
let response = provider.complete(request).await?;
println!("Response: {}", response.content);
// Check token usage
if let Some(usage) = response.usage {
println!("Tokens used: {}", usage.total_tokens);
}
# Ok(())
# }
# Provider Registry
use infrastructure_ai::{ProviderRegistry, StubAiProvider};
use std::sync::Arc;
let mut registry = ProviderRegistry::new();
registry.register(Arc::new(StubAiProvider::new(true)));
// Get an available provider
if let Some(provider) = registry.available_provider() {
println!("Using provider: {}", provider.name());
}
# Per-Tenant AI (with full feature)
use infrastructure_ai::{AiProviderFactory, TenantAiSettings, AiProviderType};
// Create factory with database pool
let factory = AiProviderFactory::new(pool);
// Load tenant settings (from preferences or database)
let settings = TenantAiSettings::new(AiProviderType::OpenRouter)
.with_model("anthropic/claude-3.5-sonnet")
.with_enabled(true);
// Get provider for tenant (loads encrypted API key from database)
let provider = factory.for_tenant(tenant_id, &settings).await;
// Use provider
let response = provider.complete(request).await?;
Capabilities
answer (other)
Reading a model's answer back as JSON.
| Item |
|---|
fn json_object(answer : & str) -> Result <serde_json::Value, String> |
fn json_as <T : DeserializeOwned>(answer : & str) -> Result <T, String> |
connection_setup (other)
Connection-setup helper for ai_credentials encryption (Sprint 0.12).
| Item |
|---|
async fn set_encryption_key(pool : & PgPool, encryption_key : & str) -> Result <()> |
async fn set_encryption_key_on_connection(conn : & mut sqlx::PgConnection, encryption_key : & str,) -> Result <()> |
How to use it
From this crate's own rustdoc:
use infrastructure_ai::{AiProvider, CompletionRequest, StubAiProvider};
# async fn example() -> infrastructure_ai::Result<()> {
// Use the stub provider for testing
let provider = StubAiProvider::new(true);
// Create a completion request
let request = CompletionRequest::new(
"You are a helpful assistant.",
"What is 2 + 2?"
)
.with_max_tokens(100)
.with_temperature(0.5);
// Get a response
let response = provider.complete(request).await?;
println!("Response: {}", response.content);
// Check token usage
if let Some(usage) = response.usage {
println!("Tokens used: {}", usage.total_tokens);
}
# Ok(())
# }
From this crate's own rustdoc:
use infrastructure_ai::{ProviderRegistry, StubAiProvider};
use std::sync::Arc;
let mut registry = ProviderRegistry::new();
registry.register(Arc::new(StubAiProvider::new(true)));
// Get an available provider
if let Some(provider) = registry.available_provider() {
println!("Using provider: {}", provider.name());
}
Module structure
infrastructure_ai
answerconnection_setup
flowchart TD n_infrastructure_ai["infrastructure_ai"] n_infrastructure_ai --> n_answer["answer"] n_infrastructure_ai --> n_connection_setup["connection_setup"]
Public surface
`answer`
| Item | What it is |
|---|---|
fn json_object(answer : & str) -> Result <serde_json::Value, String> | Pull the JSON object out of a model's answer |
fn json_as <T : DeserializeOwned>(answer : & str) -> Result <T, String> | json_object, then into the caller's type |
`connection_setup`
| Item | What it is |
|---|---|
async fn set_encryption_key(pool : & PgPool, encryption_key : & str) -> Result <()> | Set app.encryption_key on the given pool's connections |
async fn set_encryption_key_on_connection(conn : & mut sqlx::PgConnection, encryption_key : & str,) -> Result <()> | Per-connection setter — call from a sqlx after_connect hook |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
AiProviderFactory | factory::AiProviderFactory |
AnthropicProvider | anthropic::AnthropicProvider |
ClaudeCliProvider | claude_cli::ClaudeCliProvider |
OllamaProvider | ollama::OllamaProvider |
OpenRouterProvider | openrouter::OpenRouterProvider |
{AiContentPart,AiProvider,CompletionRequest,CompletionResponse,ProviderRegistry,StubAiProvider,TokenUsage,} | provider::{AiContentPart,AiProvider,CompletionRequest,CompletionResponse,ProviderRegistry,StubAiProvider,TokenUsage,} |
{AiError,Result} | error::{AiError,Result} |
{AiProviderType,TenantAiSettings} | settings::{AiProviderType,TenantAiSettings} |
{ChatMessage,ChatRequest,ChatResponse,ChatRole,FinishReason,ProviderCapabilities,ToolCall,ToolSpec,} | chat::{ChatMessage,ChatRequest,ChatResponse,ChatRole,FinishReason,ProviderCapabilities,ToolCall,ToolSpec,} |
{combine_prompt,truncate_utf8_bounded,DEFAULT_MAX_OUTPUT_BYTES,DEFAULT_TIMEOUT_SECS,} | claude_cli::{combine_prompt,truncate_utf8_bounded,DEFAULT_MAX_OUTPUT_BYTES,DEFAULT_TIMEOUT_SECS,} |
{delete_api_key,delete_system_api_key,get_api_key,get_system_api_key,has_api_key,has_system_api_key,list_providers,list_system_providers,set_api_key_enabled,set_system_api_key_enabled,setup_encryption,store_api_key,store_system_api_key,touch_api_key,ProviderInfo,} | credentials::{delete_api_key,delete_system_api_key,get_api_key,get_system_api_key,has_api_key,has_system_api_key,list_providers,list_system_providers,set_api_key_enabled,set_system_api_key_enabled,setup_encryption,store_api_key,store_system_api_key,touch_api_key,ProviderInfo,} |
{json_as,json_object} | answer::{json_as,json_object} |
Boundary
Reaches into foundation.
Shares tier infrastructure with 82 other crates: infrastructure-acquire, infrastructure-adapters-google-calendar, infrastructure-adapters-google-gmail, infrastructure-adapters-google-places, infrastructure-adapters-google-trends, infrastructure-adapters-shodan, infrastructure-adapters-yelp, infrastructure-agent, … (82 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | infrastructure |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/infrastructure/ai |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_infrastructure["infrastructure"] --> n_foundation["foundation"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `foundation-bounded-io` | foundation | no | always |
| `infrastructure-fetcher` | infrastructure | yes | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
chrono | ^0.4 | serde | yes | always |
reqwest | ^0.12 | json | yes | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
sqlx | ^0.8 | runtime-tokio, postgres, chrono, uuid, json | yes | always |
thiserror | ^2 | — | no | always |
tokio | ^1 | full | yes | always |
tracing | ^0.1 | — | no | always |
uuid | ^1 | v4, v7, serde, js | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tokio | ^1 | full, macros, rt-multi-thread | no | always |
Build. None.
Depended on by. 17 workspace crates.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR n_application_ai["application-ai"] -->|uses| SELF n_application_classify["application-classify"] -->|uses| SELF n_application_cms["application-cms"] -->|uses| SELF n_application_conversation["application-conversation"] -->|uses| SELF n_application_messaging["application-messaging"] -->|uses| SELF n_domain_ai_report["domain-ai-report"] -->|uses| SELF n_infrastructure_agent["infrastructure-agent"] -->|uses| SELF n_infrastructure_ocr_ai["infrastructure-ocr-ai"] -->|uses| SELF n_infrastructure_ocr_provider["infrastructure-ocr-provider"] -->|uses| SELF n_infrastructure_translate["infrastructure-translate"] -->|uses| SELF n_platform_privacy_scan_api["platform-privacy-scan-api"] -->|uses| SELF n_tools_doc_ask["tools-doc-ask"] -->|uses| SELF n_tools_imp["tools-imp"] -->|uses| SELF n_tools_knowitall["tools-knowitall"] -->|uses| SELF n_tools_local_ask["tools-local-ask"] -->|uses| SELF n_tools_packet_compile["tools-packet-compile"] -->|uses| SELF n_tools_prompt_shaper["tools-prompt-shaper"] -->|uses| SELF SELF["infrastructure-ai"] SELF -->|runtime| n_foundation_bounded_io["foundation-bounded-io"] SELF -->|runtime| n_infrastructure_fetcher["infrastructure-fetcher"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
| Feature | Enables | On by default |
|---|---|---|
cli | tokio | no |
default | — | yes |
full | http, postgres, cli | no |
http | reqwest, dep:infrastructure-fetcher | no |
postgres | dep:sqlx, dep:chrono | no |
reqwest | dep:reqwest | no |
tokio | dep:tokio | no |
flowchart LR n_cli["cli"] --> n_tokio["tokio"] n_default["default"] n_full["full"] --> n_http["http"] n_full["full"] --> n_postgres["postgres"] n_full["full"] --> n_cli["cli"] n_http["http"] --> n_reqwest["reqwest"] n_http["http"] --> n_dep_infrastructure_fetcher["dep:infrastructure-fetcher"] n_postgres["postgres"] --> n_dep_sqlx["dep:sqlx"] n_postgres["postgres"] --> n_dep_chrono["dep:chrono"] n_reqwest["reqwest"] --> n_dep_reqwest["dep:reqwest"] n_tokio["tokio"] --> n_dep_tokio["dep:tokio"]
Targets
| Kind | Name | Source |
|---|---|---|
| lib | infrastructure_ai | `src/lib.rs` |
| test | connection_setup_smoke | `tests/connection_setup_smoke.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | yes |
| database access | yes |
| network I/O | yes |
| unsafe code | none detected |
| environment variables | yes |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
| Variable | Read in |
|---|---|
ANTHROPIC_API_KEY | src/anthropic.rs |
ANTHROPIC_MODEL | src/anthropic.rs |
CLAUDE_BIN | src/claude_cli.rs |
OLLAMA_BASE_URL | src/factory.rs |
OLLAMA_MODEL | src/ollama.rs |
OPENROUTER_API_KEY | src/openrouter.rs |
OPENROUTER_MODEL | src/openrouter.rs |
Related capabilities
17 workspace crates depend on this one: application-ai, application-classify, application-cms, application-conversation, application-messaging, domain-ai-report, infrastructure-agent, infrastructure-ocr-ai, infrastructure-ocr-provider, infrastructure-translate, platform-privacy-scan-api, tools-doc-ask, … (17 total).
Verification
| Kind | Count |
|---|---|
| Unit tests | 70 |
| Integration tests | 2 |
| Examples | 0 |
| Doctests | 3 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
answer | 2 | 0 | 0 |
connection_setup | 2 | 0 | 1 |
What the tests establish, by name:
after_connect_hook_lets_inserts_succeed—tests/connection_setup_smoke.rswithout_hook_inserts_are_rejected—tests/connection_setup_smoke.rsa_bare_object_is_read_as_it_stands—src/answer.rsa_fence_around_the_object_is_not_part_of_the_object—src/answer.rsa_missing_field_is_still_missing_after_unwrapping—src/answer.rsa_result_that_is_not_a_string_is_left_alone—src/answer.rsan_answer_with_no_object_says_so_rather_than_guessing—src/answer.rsjson_as_reads_the_unwrapped_object_into_the_callers_type—src/answer.rsprose_on_either_side_of_the_object_is_ignored—src/answer.rssomething_between_braces_that_is_not_json_is_reported_as_not_json—src/answer.rsthe_answer_inside_a_worker_envelope_is_the_answer—src/answer.rsdebug_does_not_leak_api_key—src/anthropic.rstest_available_models—src/anthropic.rstest_empty_key_not_available—src/anthropic.rstest_image_content_serializes_as_blocks—src/anthropic.rstest_image_only_content_serializes_single_block—src/anthropic.rstest_provider_creation—src/anthropic.rstest_text_only_content_serializes_as_string—src/anthropic.rsassistant_tool_calls_has_empty_content—src/chat.rscapabilities_builder—src/chat.rsrequest_builder_sets_fields—src/chat.rsresponse_accessors—src/chat.rstool_calls_skip_serializing_when_empty—src/chat.rstool_result_roundtrips_role_and_name—src/chat.rsclaude_cli_real_translation—src/claude_cli.rsclaude_cli_real_vision_ocr—src/claude_cli.rscombine_prompt_does_not_escape_or_alter_metacharacters—src/claude_cli.rscombine_prompt_joins_system_and_user—src/claude_cli.rsmalicious_input_never_reaches_a_shell—src/claude_cli.rstruncate_bounded_respects_cap_and_codepoints—src/claude_cli.rs- _… 42 more_
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 4 | 4 |
Public modules with a //! block | 2 | 2 |
pie showData
title Public items with rustdoc
"Documented" : 4
"No rustdoc detected" : 0
Metrics
| Metric | Value |
|---|---|
| Rust source files | 13 |
| Source lines | 4395 |
| Code lines | 3089 |
| Public API items | 4 |
| Public modules | 2 |
| Tests | 72 |
| Examples | 0 |
| Cargo features | 7 |
| Direct runtime dependencies | 12 |
| Workspace reverse dependencies | 17 |
pie showData
title Public API by kind
"function" : 4
pie showData
title Rust source composition
"Code" : 3089
"Blank or comment" : 1306
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.