fix: resolve all 75 mem-cli compilation errors across 25 files
CI / CI (pull_request) Successful in 4m17s

- embed_text -> embed_one + Vector to Vec<f32> conversion (semantic.rs, unified_query.rs)
- DefaultAgent: remove shadowing type alias, re-export concrete struct
- Position: add Default derive for unwrap_or_default()
- streaming_body -> streaming + yield Result<Bytes> for SSE
- borrow-after-move: compute len before move in 6 places
- add missing imports: sqlx::Row, chrono::Timelike, std::pin::Pin
- add missing derives: Serialize on CompactionStats, FacetFilters
- extract_token: extract Authorization header from HttpRequest first
- AuthError variants: match actual enum (TokenExpired, not ExpiredToken)
- validate_bearer_token -> extract_bearer_token (sync check)
- check_limit -> check with correct args
- link_entities -> link_mentions (async, correct signature)
- InferenceEngine::new + infer_facts: match actual 2-arg/3-arg API
- RoutedResult: add missing confidence_score + is_valid fields
- client_sdk: fix ownership (remove borrow, save status before .text())
- index_chunk -> index_document (match OpenSearchClient API)
- recursive async dfs_paths: Box::pin for infinite future size
- log -> tracing crate in authentik_service_account
- CI: add SQLX_OFFLINE=true env var for offline builds
This commit is contained in:
2026-09-07 17:54:29 -07:00
parent ce6ddc2b3d
commit cc4ba87e2d
25 changed files with 99 additions and 81 deletions
+1
View File
@@ -11,6 +11,7 @@ env:
REGISTRY: forgejo.riotpiao.com REGISTRY: forgejo.riotpiao.com
IMAGE: forgejo.riotpiao.com/rock/poimen-memory IMAGE: forgejo.riotpiao.com/rock/poimen-memory
DOCKER_HOST: tcp://localhost:2375 DOCKER_HOST: tcp://localhost:2375
SQLX_OFFLINE: "true"
jobs: jobs:
ci: ci:
+3 -2
View File
@@ -227,7 +227,7 @@ impl SynthesisClient {
) -> Vec<Result<ClientResponse, String>> { ) -> Vec<Result<ClientResponse, String>> {
let mut results = Vec::new(); let mut results = Vec::new();
for req in requests { for req in requests {
results.push(self.execute(&req).await); results.push(self.execute(req).await);
} }
results results
} }
@@ -280,11 +280,12 @@ impl SynthesisClient {
tracing::debug!("Workflow executed in {}ms", elapsed_ms); tracing::debug!("Workflow executed in {}ms", elapsed_ms);
Ok(body) Ok(body)
} else { } else {
let status = response.status();
let error_text = response let error_text = response
.text() .text()
.await .await
.unwrap_or_else(|_| "unknown error".to_string()); .unwrap_or_else(|_| "unknown error".to_string());
Err(format!("Workflow failed ({}): {}", response.status(), error_text)) Err(format!("Workflow failed ({}): {}", status, error_text))
} }
} }
} }
+1 -3
View File
@@ -11,6 +11,4 @@ pub use agent_interface::{Agent, AgentConfig, AgentCapability};
pub use webhook_handler::{WebhookEvent, WebhookPayload}; pub use webhook_handler::{WebhookEvent, WebhookPayload};
pub use observability::{AgentMetrics, MetricsCollector}; pub use observability::{AgentMetrics, MetricsCollector};
pub use client_sdk::{SynthesisClient, ClientRequest, ClientResponse}; pub use client_sdk::{SynthesisClient, ClientRequest, ClientResponse};
pub use agent_interface::DefaultAgent;
/// Default agent implementation (type alias for Agent)
pub type DefaultAgent = Agent;
@@ -5,7 +5,7 @@ use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use reqwest::Client; use reqwest::Client;
use log::{debug, warn, error}; use tracing::{debug, warn, error};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct AuthentikServiceAccountConfig { pub struct AuthentikServiceAccountConfig {
+5 -3
View File
@@ -59,15 +59,17 @@ pub fn auth_error_response(error: &AuthError) -> HttpResponse {
let (status, message) = match error { let (status, message) = match error {
AuthError::MissingToken => ("Unauthorized", "Missing or invalid Authorization header"), AuthError::MissingToken => ("Unauthorized", "Missing or invalid Authorization header"),
AuthError::InvalidSignature => ("Unauthorized", "Invalid token signature"), AuthError::InvalidSignature => ("Unauthorized", "Invalid token signature"),
AuthError::ExpiredToken => ("Unauthorized", "Token has expired"), AuthError::TokenExpired => ("Unauthorized", "Token has expired"),
AuthError::InvalidIssuer => ("Unauthorized", "Invalid token issuer"), AuthError::InvalidIssuer => ("Unauthorized", "Invalid token issuer"),
AuthError::AccessDenied => ("Forbidden", "Access denied for this resource"), AuthError::InvalidAudience => ("Unauthorized", "Invalid token audience"),
AuthError::InvalidClaims => ("Unauthorized", "Invalid or missing required claims"), AuthError::ProviderUnavailable(_) => ("ServiceUnavailable", "Auth provider unavailable"),
AuthError::Other(_) => ("Unauthorized", "Authentication error"),
}; };
HttpResponse::build(match status { HttpResponse::build(match status {
"Unauthorized" => actix_web::http::StatusCode::UNAUTHORIZED, "Unauthorized" => actix_web::http::StatusCode::UNAUTHORIZED,
"Forbidden" => actix_web::http::StatusCode::FORBIDDEN, "Forbidden" => actix_web::http::StatusCode::FORBIDDEN,
"ServiceUnavailable" => actix_web::http::StatusCode::SERVICE_UNAVAILABLE,
_ => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, _ => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
}) })
.json(json!({ .json(json!({
+1 -1
View File
@@ -19,7 +19,7 @@ pub trait LlmCaller: Send + Sync {
} }
/// Compaction statistics /// Compaction statistics
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, serde::Serialize)]
pub struct CompactionStats { pub struct CompactionStats {
pub duplicate_edges_deleted: usize, pub duplicate_edges_deleted: usize,
pub stale_facts_deleted: usize, pub stale_facts_deleted: usize,
+3 -3
View File
@@ -41,7 +41,7 @@ pub fn validate_and_rate_limit(
})) }))
})?; })?;
jwt_validator.validate_bearer_token(auth_header).map_err(|e| { crate::jwt_validator::JwtValidator::extract_bearer_token(auth_header).map_err(|e| {
HttpResponse::Unauthorized().json(json!({ HttpResponse::Unauthorized().json(json!({
"error": format!("JWT validation failed: {}", e) "error": format!("JWT validation failed: {}", e)
})) }))
@@ -51,10 +51,10 @@ pub fn validate_and_rate_limit(
// 2. Rate limiting (if enabled) // 2. Rate limiting (if enabled)
state state
.rate_limiter .rate_limiter
.check_limit(endpoint, rate_limit) .check("default", endpoint)
.map_err(|e| { .map_err(|e| {
HttpResponse::TooManyRequests().json(json!({ HttpResponse::TooManyRequests().json(json!({
"error": format!("Rate limit exceeded: {}", e) "error": format!("Rate limit exceeded: {}", e.reason())
})) }))
})?; })?;
@@ -171,7 +171,7 @@ pub struct RankedResult {
/// GET /memory/ranking/profiles /// GET /memory/ranking/profiles
pub async fn get_ranking_profiles(req: HttpRequest) -> HttpResponse { pub async fn get_ranking_profiles(req: HttpRequest) -> HttpResponse {
// Verify auth // Verify auth
if let Err(e) = AuthGuard::extract_token(&req) { if let Err(e) = AuthGuard::extract_token(req.headers().get("Authorization").and_then(|v| v.to_str().ok()).unwrap_or("")) {
return HttpResponse::Unauthorized().json(json!({ return HttpResponse::Unauthorized().json(json!({
"error": e.to_string() "error": e.to_string()
})); }));
@@ -54,7 +54,7 @@ pub async fn rebuild(
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
) -> HttpResponse { ) -> HttpResponse {
// Verify auth // Verify auth
if let Err(e) = AuthGuard::extract_token(&req) { if let Err(e) = AuthGuard::extract_token(req.headers().get("Authorization").and_then(|v| v.to_str().ok()).unwrap_or("")) {
return HttpResponse::Unauthorized().json(json!({ return HttpResponse::Unauthorized().json(json!({
"error": e.to_string() "error": e.to_string()
})); }));
@@ -155,7 +155,7 @@ pub async fn rebuild_status(
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
) -> HttpResponse { ) -> HttpResponse {
// Verify auth // Verify auth
if let Err(e) = AuthGuard::extract_token(&req) { if let Err(e) = AuthGuard::extract_token(req.headers().get("Authorization").and_then(|v| v.to_str().ok()).unwrap_or("")) {
return HttpResponse::Unauthorized().json(json!({ return HttpResponse::Unauthorized().json(json!({
"error": e.to_string() "error": e.to_string()
})); }));
+6 -6
View File
@@ -142,8 +142,8 @@ pub async fn search_entities_handler(
body.query, body.entity_type, body.start_time, body.end_time); body.query, body.entity_type, body.start_time, body.end_time);
// 3. Embed query // 3. Embed query
let query_embedding = match state.embeddings.embed_text(&body.query).await { let query_embedding = match state.embeddings.embed_one(&body.query).await {
Ok(emb) => emb, Ok(emb) => emb.to_vec(),
Err(e) => { Err(e) => {
error!("Embedding failed: {}", e); error!("Embedding failed: {}", e);
return crate::handlers::response_builder::internal_error( return crate::handlers::response_builder::internal_error(
@@ -278,8 +278,8 @@ pub async fn search_edges_handler(
body.query, body.relation_type, body.start_time, body.end_time); body.query, body.relation_type, body.start_time, body.end_time);
// 3. Embed query // 3. Embed query
let query_embedding = match state.embeddings.embed_text(&body.query).await { let query_embedding = match state.embeddings.embed_one(&body.query).await {
Ok(emb) => emb, Ok(emb) => emb.to_vec(),
Err(e) => { Err(e) => {
error!("Embedding failed: {}", e); error!("Embedding failed: {}", e);
return crate::handlers::response_builder::internal_error( return crate::handlers::response_builder::internal_error(
@@ -361,8 +361,8 @@ pub async fn hybrid_search_handler(
body.query, body.semantic_weight, body.lexical_weight); body.query, body.semantic_weight, body.lexical_weight);
// 3. Embed query // 3. Embed query
let query_embedding = match state.embeddings.embed_text(&body.query).await { let query_embedding = match state.embeddings.embed_one(&body.query).await {
Ok(emb) => emb, Ok(emb) => emb.to_vec(),
Err(e) => { Err(e) => {
error!("Embedding failed: {}", e); error!("Embedding failed: {}", e);
return crate::handlers::response_builder::internal_error( return crate::handlers::response_builder::internal_error(
+2 -1
View File
@@ -517,11 +517,12 @@ pub async fn reasoning_paths_handler(
let elapsed = start_time.elapsed().as_millis(); let elapsed = start_time.elapsed().as_millis();
info!("Paths: {} found in {}ms", paths.len(), elapsed); info!("Paths: {} found in {}ms", paths.len(), elapsed);
let path_count = paths.len();
crate::handlers::response_builder::success_response(ReasoningPathsResponse { crate::handlers::response_builder::success_response(ReasoningPathsResponse {
source_id: body.source_id.clone(), source_id: body.source_id.clone(),
target_id: body.target_id.clone(), target_id: body.target_id.clone(),
paths, paths,
path_count: paths.len(), path_count,
process_time_ms: elapsed, process_time_ms: elapsed,
}) })
} }
+2 -2
View File
@@ -136,8 +136,8 @@ pub async fn unified_query_handler(
body.search_type, body.query, body.entity_type, body.relation_type); body.search_type, body.query, body.entity_type, body.relation_type);
// 3. Embed query once (reused for all search types) // 3. Embed query once (reused for all search types)
let query_embedding = match state.embeddings.embed_text(&body.query).await { let query_embedding = match state.embeddings.embed_one(&body.query).await {
Ok(emb) => emb, Ok(emb) => emb.to_vec(),
Err(e) => { Err(e) => {
error!("Embedding failed: {}", e); error!("Embedding failed: {}", e);
return crate::handlers::response_builder::internal_error( return crate::handlers::response_builder::internal_error(
@@ -158,12 +158,12 @@ pub async fn unified_synthesis_handler(
// Entity Linking // Entity Linking
if body.link_entities { if body.link_entities {
let linker = EntityLinker::new(state.pool.clone()); let linker = EntityLinker::new(state.pool.clone());
match linker.link_entities(&body.content) { match linker.link_mentions(&body.content, &body.project).await {
Ok(links) => { Ok((links, _unlinked)) => {
let alias_count = links.iter().filter(|l| l.confidence > 0.85).count(); let alias_count = links.iter().filter(|l| l.confidence > 0.85).count();
entity_linking = Some(EntityLinkingResult { entity_linking = Some(EntityLinkingResult {
mention_links: links.iter().map(|l| MentionLinkResponse { mention_links: links.iter().map(|l| MentionLinkResponse {
mention: l.mention.clone(), mention: l.mention_text.clone(),
entity_id: l.entity_id.clone(), entity_id: l.entity_id.clone(),
confidence: l.confidence, confidence: l.confidence,
}).collect(), }).collect(),
@@ -179,14 +179,14 @@ pub async fn unified_synthesis_handler(
// Inference // Inference
if body.infer_facts { if body.infer_facts {
let engine = InferenceEngine::new(state.pool.clone()); let engine = InferenceEngine::new(state.pool.clone(), vec![]);
match engine.infer_facts(&body.content, 5, 0.6, &body.project) { match engine.infer_facts(&body.project, &body.content, 5).await {
Ok(facts) => { Ok(facts) => {
inference = Some(InferenceResult { inference = Some(InferenceResult {
inferred_facts: facts.iter().map(|f| InferredFactResponse { inferred_facts: facts.iter().map(|f| InferredFactResponse {
source: f.source.clone(), source: f.source_id.clone(),
relation: f.relation.clone(), relation: f.relation_type.clone(),
target: f.target.clone(), target: f.target_id.clone(),
confidence: f.confidence, confidence: f.confidence,
}).collect(), }).collect(),
fact_count: facts.len(), fact_count: facts.len(),
@@ -15,7 +15,7 @@ pub async fn get_entity_versions(
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
) -> HttpResponse { ) -> HttpResponse {
// Verify auth // Verify auth
if let Err(e) = AuthGuard::extract_token(&req) { if let Err(e) = AuthGuard::extract_token(req.headers().get("Authorization").and_then(|v| v.to_str().ok()).unwrap_or("")) {
return HttpResponse::Unauthorized().json(json!({ return HttpResponse::Unauthorized().json(json!({
"error": e.to_string() "error": e.to_string()
})); }));
@@ -46,7 +46,7 @@ pub async fn get_entity_version(
path: web::Path<(String, i32)>, path: web::Path<(String, i32)>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
) -> HttpResponse { ) -> HttpResponse {
if let Err(e) = AuthGuard::extract_token(&req) { if let Err(e) = AuthGuard::extract_token(req.headers().get("Authorization").and_then(|v| v.to_str().ok()).unwrap_or("")) {
return HttpResponse::Unauthorized().json(json!({ return HttpResponse::Unauthorized().json(json!({
"error": e.to_string() "error": e.to_string()
})); }));
@@ -80,7 +80,7 @@ pub async fn get_entity_diff(
query: web::Query<DiffQuery>, query: web::Query<DiffQuery>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
) -> HttpResponse { ) -> HttpResponse {
if let Err(e) = AuthGuard::extract_token(&req) { if let Err(e) = AuthGuard::extract_token(req.headers().get("Authorization").and_then(|v| v.to_str().ok()).unwrap_or("")) {
return HttpResponse::Unauthorized().json(json!({ return HttpResponse::Unauthorized().json(json!({
"error": e.to_string() "error": e.to_string()
})); }));
@@ -120,7 +120,7 @@ pub async fn get_entity_at_time(
query: web::Query<TimeQuery>, query: web::Query<TimeQuery>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
) -> HttpResponse { ) -> HttpResponse {
if let Err(e) = AuthGuard::extract_token(&req) { if let Err(e) = AuthGuard::extract_token(req.headers().get("Authorization").and_then(|v| v.to_str().ok()).unwrap_or("")) {
return HttpResponse::Unauthorized().json(json!({ return HttpResponse::Unauthorized().json(json!({
"error": e.to_string() "error": e.to_string()
})); }));
@@ -164,7 +164,7 @@ pub async fn get_edge_versions(
path: web::Path<Uuid>, path: web::Path<Uuid>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
) -> HttpResponse { ) -> HttpResponse {
if let Err(e) = AuthGuard::extract_token(&req) { if let Err(e) = AuthGuard::extract_token(req.headers().get("Authorization").and_then(|v| v.to_str().ok()).unwrap_or("")) {
return HttpResponse::Unauthorized().json(json!({ return HttpResponse::Unauthorized().json(json!({
"error": e.to_string() "error": e.to_string()
})); }));
@@ -196,7 +196,7 @@ pub async fn get_edge_diff(
query: web::Query<DiffQuery>, query: web::Query<DiffQuery>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
) -> HttpResponse { ) -> HttpResponse {
if let Err(e) = AuthGuard::extract_token(&req) { if let Err(e) = AuthGuard::extract_token(req.headers().get("Authorization").and_then(|v| v.to_str().ok()).unwrap_or("")) {
return HttpResponse::Unauthorized().json(json!({ return HttpResponse::Unauthorized().json(json!({
"error": e.to_string() "error": e.to_string()
})); }));
+5 -3
View File
@@ -145,13 +145,15 @@ pub async fn visualize_stream_handler(
match execute_streaming_visualization(&state, req_body).await { match execute_streaming_visualization(&state, req_body).await {
Ok(events) => { Ok(events) => {
for event in events { for event in events {
yield format_sse_event(event); let data = format_sse_event(event);
yield Ok::<actix_web::web::Bytes, actix_web::Error>(actix_web::web::Bytes::from(data));
} }
} }
Err(e) => { Err(e) => {
yield format_sse_event(VisualizeEvent::Error { let data = format_sse_event(VisualizeEvent::Error {
message: e, message: e,
}); });
yield Ok::<actix_web::web::Bytes, actix_web::Error>(actix_web::web::Bytes::from(data));
} }
} }
}; };
@@ -161,7 +163,7 @@ pub async fn visualize_stream_handler(
.insert_header(("Cache-Control", "no-cache")) .insert_header(("Cache-Control", "no-cache"))
.insert_header(("Connection", "keep-alive")) .insert_header(("Connection", "keep-alive"))
.insert_header(("Transfer-Encoding", "chunked")) .insert_header(("Transfer-Encoding", "chunked"))
.streaming_body(Box::pin(stream)) .streaming(Box::pin(stream))
} }
/// Execute streaming visualization (generates events) /// Execute streaming visualization (generates events)
+5 -5
View File
@@ -116,13 +116,13 @@ impl ParallelDualWriteIndexer {
// Spawn background task (non-blocking) // Spawn background task (non-blocking)
tokio::spawn(async move { tokio::spawn(async move {
let result = opensearch.index_chunk( let result = opensearch.index_document(
&chunk_id, &chunk_id,
&chunk.content, &chunk.content,
&chunk.source, &chunk.source,
&chunk.project,
&chunk.level, &chunk.level,
&chunk.breadcrumb.join(" > "), chunk.breadcrumb.clone(),
"", // jwt_token - not available in background task
).await; ).await;
match result { match result {
@@ -139,8 +139,8 @@ impl ParallelDualWriteIndexer {
&self, &self,
chunks: Vec<(&IndexableChunk, Vec<f32>)>, chunks: Vec<(&IndexableChunk, Vec<f32>)>,
) -> Vec<DualWriteResult> { ) -> Vec<DualWriteResult> {
let futures = chunks.into_iter().map(|(chunk, embedding)| { let futures = chunks.into_iter().map(|(chunk, embedding)| async move {
self.index_parallel(chunk, &embedding) self.index_parallel(chunk, &embedding).await
}); });
futures::future::join_all(futures) futures::future::join_all(futures)
@@ -6,7 +6,7 @@
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use sqlx::{Pool, Postgres}; use sqlx::{Pool, Postgres, Row};
/// A node in the traversal result /// A node in the traversal result
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -196,6 +196,7 @@ impl BfsGraphTraversal {
}); });
} }
let edge_count = edges.len();
Ok(GraphData { Ok(GraphData {
nodes, nodes,
edges, edges,
@@ -203,7 +204,7 @@ impl BfsGraphTraversal {
requested_depth: config.max_depth, requested_depth: config.max_depth,
max_depth_reached: max_depth, max_depth_reached: max_depth,
node_count: visited.len(), node_count: visited.len(),
edge_count: edges.len(), edge_count,
depth_breakdown, depth_breakdown,
traversal_time_ms: start_time.elapsed().as_millis() as u64, traversal_time_ms: start_time.elapsed().as_millis() as u64,
}) })
@@ -185,9 +185,9 @@ impl CommunityDetector {
communities_vec.push(Community { communities_vec.push(Community {
id: comm_id, id: comm_id,
size: members.len(),
entity_ids: members.into_iter().collect(), entity_ids: members.into_iter().collect(),
entity_names, entity_names,
size: members.len(),
modularity_contribution: modularity_contrib, modularity_contribution: modularity_contrib,
average_strength: strength, average_strength: strength,
density, density,
@@ -196,9 +196,9 @@ impl CommunityDetector {
} }
// 5. Calculate total modularity // 5. Calculate total modularity
let total_modularity = communities_vec let total_modularity: f64 = communities_vec
.iter() .iter()
.map(|c| c.modularity_contribution) .map(|c| c.modularity_contribution as f64)
.sum(); .sum();
let average_community_size = if communities_vec.is_empty() { let average_community_size = if communities_vec.is_empty() {
@@ -210,9 +210,9 @@ impl CommunityDetector {
let result = CommunityDetectionResult { let result = CommunityDetectionResult {
entity_count: entities.len(), entity_count: entities.len(),
edge_count: edges.len(), edge_count: edges.len(),
communities: communities_vec,
community_count: communities_vec.len(), community_count: communities_vec.len(),
total_modularity: total_modularity.max(-1.0).min(1.0), communities: communities_vec,
total_modularity: total_modularity.max(-1.0).min(1.0) as f32,
average_community_size, average_community_size,
}; };
+2 -2
View File
@@ -3,7 +3,7 @@
//! Enables multi-dimensional filtering across entities and edges. //! Enables multi-dimensional filtering across entities and edges.
//! Supports entity types, relation types, date ranges, confidence levels, and more. //! Supports entity types, relation types, date ranges, confidence levels, and more.
use chrono::{DateTime, Utc}; use chrono::{DateTime, Timelike, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{Pool, Postgres}; use sqlx::{Pool, Postgres};
use std::collections::HashMap; use std::collections::HashMap;
@@ -42,7 +42,7 @@ pub struct AvailableFacets {
} }
/// Facet filters for a query /// Facet filters for a query
#[derive(Debug, Clone, Default, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FacetFilters { pub struct FacetFilters {
/// Filter by entity types (OR within facet, AND across facets) /// Filter by entity types (OR within facet, AND across facets)
pub entity_types: Option<Vec<String>>, pub entity_types: Option<Vec<String>>,
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use super::bfs_graph_traversal::{GraphData, TraversalNode, TraversalEdge}; use super::bfs_graph_traversal::{GraphData, TraversalNode, TraversalEdge};
/// 2D position (X, Y coordinates) /// 2D position (X, Y coordinates)
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Position { pub struct Position {
pub x: f32, pub x: f32,
pub y: f32, pub y: f32,
+15 -11
View File
@@ -4,6 +4,8 @@
//! confidence propagation through reasoning chains. //! confidence propagation through reasoning chains.
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
use std::pin::Pin;
use std::future::Future;
use sqlx::PgPool; use sqlx::PgPool;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{debug, warn}; use tracing::{debug, warn};
@@ -293,18 +295,19 @@ impl InferenceEngine {
} }
/// DFS to find all paths /// DFS to find all paths
async fn dfs_paths( fn dfs_paths<'a>(
&self, &'a self,
current: &str, current: &'a str,
target: &str, target: &'a str,
project_id: &str, project_id: &'a str,
remaining_hops: usize, remaining_hops: usize,
path: &mut Vec<String>, path: &'a mut Vec<String>,
relations: &mut Vec<String>, relations: &'a mut Vec<String>,
confidences: &mut Vec<f32>, confidences: &'a mut Vec<f32>,
visited: &mut HashSet<String>, visited: &'a mut HashSet<String>,
results: &mut Vec<ReasoningPath>, results: &'a mut Vec<ReasoningPath>,
) -> Result<(), String> { ) -> Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'a>> {
Box::pin(async move {
if remaining_hops == 0 { if remaining_hops == 0 {
return Ok(()); return Ok(());
} }
@@ -350,6 +353,7 @@ impl InferenceEngine {
} }
Ok(()) Ok(())
}) // Box::pin
} }
} }
+15 -9
View File
@@ -6,6 +6,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{Pool, Postgres}; use sqlx::{Pool, Postgres};
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
use std::pin::Pin;
use std::future::Future;
use tracing::{debug, info}; use tracing::{debug, info};
/// A single path through the graph /// A single path through the graph
@@ -123,13 +125,14 @@ impl PathFinder {
info!("Found shortest path: {} → {} (distance: {})", info!("Found shortest path: {} → {} (distance: {})",
source_id, target_id, final_entities.len() - 1); source_id, target_id, final_entities.len() - 1);
let distance = final_entities.len() - 1;
return Ok(Some(Path { return Ok(Some(Path {
source_id: source_id.to_string(), source_id: source_id.to_string(),
target_id: target_id.to_string(), target_id: target_id.to_string(),
entity_ids: final_entities, entity_ids: final_entities,
entity_names: vec![], // Could fetch from DB if needed entity_names: vec![], // Could fetch from DB if needed
relation_types: final_relations, relation_types: final_relations,
distance: final_entities.len() - 1, distance,
total_confidence: final_confidence.max(0.0).min(1.0), total_confidence: final_confidence.max(0.0).min(1.0),
})); }));
} }
@@ -293,19 +296,20 @@ impl PathFinder {
} }
/// DFS helper for finding all paths /// DFS helper for finding all paths
async fn dfs_paths( fn dfs_paths<'a>(
&self, &'a self,
source_id: &str, source_id: &'a str,
target_id: &str, target_id: &'a str,
current_path: Vec<String>, current_path: Vec<String>,
relations_path: Vec<String>, relations_path: Vec<String>,
confidence: f32, confidence: f32,
depth: usize, depth: usize,
max_depth: usize, max_depth: usize,
paths_found: &mut Vec<Path>, paths_found: &'a mut Vec<Path>,
visited: &mut HashSet<String>, visited: &'a mut HashSet<String>,
max_paths: usize, max_paths: usize,
) -> Result<(), String> { ) -> Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'a>> {
Box::pin(async move {
if paths_found.len() >= max_paths { if paths_found.len() >= max_paths {
return Ok(()); // Found enough paths return Ok(()); // Found enough paths
} }
@@ -328,13 +332,14 @@ impl PathFinder {
let final_confidence = confidence * edge.confidence; let final_confidence = confidence * edge.confidence;
let distance = final_path.len() - 1;
paths_found.push(Path { paths_found.push(Path {
source_id: source_id.to_string(), source_id: source_id.to_string(),
target_id: target_id.to_string(), target_id: target_id.to_string(),
entity_ids: final_path, entity_ids: final_path,
entity_names: vec![], entity_names: vec![],
relation_types: final_relations, relation_types: final_relations,
distance: final_path.len() - 1, distance,
total_confidence: final_confidence.max(0.0).min(1.0), total_confidence: final_confidence.max(0.0).min(1.0),
}); });
@@ -370,6 +375,7 @@ impl PathFinder {
} }
Ok(()) Ok(())
}) // Box::pin
} }
/// Fetch direct neighbors of an entity /// Fetch direct neighbors of an entity
@@ -138,7 +138,7 @@ impl SemanticRetriever {
.await .await
.map_err(|e| format!("Database error: {}", e))?; .map_err(|e| format!("Database error: {}", e))?;
let entities = results let entities: Vec<_> = results
.into_iter() .into_iter()
.map(|(id, name, entity_type, score, metadata)| EntityResult { .map(|(id, name, entity_type, score, metadata)| EntityResult {
id, id,
@@ -213,7 +213,7 @@ impl SemanticRetriever {
.await .await
.map_err(|e| format!("Database error: {}", e))?; .map_err(|e| format!("Database error: {}", e))?;
let edges = results let edges: Vec<_> = results
.into_iter() .into_iter()
.map(|(id, src_id, tgt_id, src_name, tgt_name, rel_type, fact, score, conf)| { .map(|(id, src_id, tgt_id, src_name, tgt_name, rel_type, fact, score, conf)| {
EdgeResult { EdgeResult {
+2 -2
View File
@@ -261,7 +261,7 @@ impl Summarizer {
} }
/// Split text into sentences /// Split text into sentences
fn split_sentences(&self, text: &str) -> Vec<&str> { fn split_sentences<'a>(&self, text: &'a str) -> Vec<&'a str> {
text.split('.').map(|s| s.trim()).filter(|s| !s.is_empty()).collect() text.split('.').map(|s| s.trim()).filter(|s| !s.is_empty()).collect()
} }
@@ -331,7 +331,7 @@ impl Summarizer {
let overlap = entities1 let overlap = entities1
.iter() .iter()
.filter(|e| entities2.contains(e)) .filter(|e| entities2.contains(*e))
.count(); .count();
coherence += overlap as f32 / (entities1.len().max(entities2.len()) as f32).max(1.0); coherence += overlap as f32 / (entities1.len().max(entities2.len()) as f32).max(1.0);
} }
+2
View File
@@ -245,6 +245,8 @@ impl QueryRouter {
prefilter_size, prefilter_size,
metrics, metrics,
latency_ms, latency_ms,
confidence_score: 1.0,
is_valid: true,
}) })
} }