Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c301d7e91 | ||
|
|
39f0bf798c | ||
|
|
2f65f71bfb |
@@ -50,13 +50,19 @@ jobs:
|
|||||||
REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
||||||
REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push Docker image (SHA tag only)
|
- name: Build Docker image
|
||||||
run: |
|
run: |
|
||||||
docker build --no-cache --progress=plain \
|
docker build --no-cache --progress=plain \
|
||||||
-t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \
|
-t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \
|
||||||
|
-t "${IMAGE}:latest" \
|
||||||
-f Dockerfile .
|
-f Dockerfile .
|
||||||
|
|
||||||
|
- name: Push Docker image
|
||||||
|
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||||
|
run: |
|
||||||
docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
||||||
echo "Pushed: ${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
docker push "${IMAGE}:latest"
|
||||||
|
echo "✓ Pushed: ${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
||||||
|
|
||||||
- name: Prune unused images
|
- name: Prune unused images
|
||||||
run: docker image prune -a --force 2>&1 | tail -3 || true
|
run: docker image prune -a --force 2>&1 | tail -3 || true
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
name: Deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: forgejo.riotpiao.com
|
|
||||||
IMAGE: forgejo.riotpiao.com/riotpiao-poimen/poimen-memory
|
|
||||||
DOCKER_HOST: tcp://localhost:2375
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: Tag & Push Latest
|
|
||||||
runs-on: rust
|
|
||||||
steps:
|
|
||||||
- name: Install Docker
|
|
||||||
run: apt-get update && apt-get install -y docker.io
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get short SHA
|
|
||||||
id: sha
|
|
||||||
run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Registry login
|
|
||||||
run: |
|
|
||||||
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" \
|
|
||||||
--username "${REGISTRY_USER}" --password-stdin
|
|
||||||
env:
|
|
||||||
REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
|
||||||
REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
|
||||||
|
|
||||||
- name: Pull SHA image and tag as latest
|
|
||||||
run: |
|
|
||||||
docker pull "${IMAGE}:${{ steps.sha.outputs.short_sha }}" && \
|
|
||||||
docker tag "${IMAGE}:${{ steps.sha.outputs.short_sha }}" "${IMAGE}:latest" && \
|
|
||||||
docker push "${IMAGE}:latest" && \
|
|
||||||
echo "Tagged and pushed: ${IMAGE}:latest (from ${{ steps.sha.outputs.short_sha }})"
|
|
||||||
|
|
||||||
- name: Prune images
|
|
||||||
run: docker image prune -a --force 2>&1 | tail -3 || true
|
|
||||||
@@ -2,7 +2,7 @@ use anyhow::Result;
|
|||||||
use mem_store::{MemoryL1, VectorStore, ChunkL0, EntityRepoOps, EdgeRepoOps};
|
use mem_store::{MemoryL1, VectorStore, ChunkL0, EntityRepoOps, EdgeRepoOps};
|
||||||
use mem_llm::EmbeddingsClient;
|
use mem_llm::EmbeddingsClient;
|
||||||
use mem_ingest::ingest_pipeline::{IngestPipeline, Episode};
|
use mem_ingest::ingest_pipeline::{IngestPipeline, Episode};
|
||||||
use mem_ingest::entity_extractor::WikiLinkFallbackExtractor;
|
use mem_ingest::entity_extractor::{WikiLinkFallbackExtractor, LlmEntityExtractor};
|
||||||
use mem_ingest::fact_extractor::SimpleFactExtractor;
|
use mem_ingest::fact_extractor::SimpleFactExtractor;
|
||||||
use mem_ingest::contradiction_detector::ContradictionHandler;
|
use mem_ingest::contradiction_detector::ContradictionHandler;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
@@ -26,9 +26,16 @@ impl IngestWorker {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let vector_store = Arc::new(VectorStore::new(pool.clone()));
|
let vector_store = Arc::new(VectorStore::new(pool.clone()));
|
||||||
|
|
||||||
// Initialize extraction pipeline
|
// Initialize extraction pipeline — use LLM if LLM_ENDPOINT is set, else fallback to wiki links
|
||||||
let entity_extractor: Arc<dyn mem_ingest::entity_extractor::EntityExtractor> =
|
let entity_extractor: Arc<dyn mem_ingest::entity_extractor::EntityExtractor> =
|
||||||
Arc::new(WikiLinkFallbackExtractor);
|
if std::env::var("LLM_ENDPOINT").is_ok() {
|
||||||
|
let model = std::env::var("LLM_MODEL").unwrap_or_else(|_| "qwen2.5:3b-instruct".to_string());
|
||||||
|
tracing::info!("Using LLM entity extractor: model={}", model);
|
||||||
|
Arc::new(LlmEntityExtractor::new(&model))
|
||||||
|
} else {
|
||||||
|
tracing::info!("LLM_ENDPOINT not set, using WikiLink fallback extractor");
|
||||||
|
Arc::new(WikiLinkFallbackExtractor)
|
||||||
|
};
|
||||||
let fact_extractor: Arc<dyn mem_ingest::fact_extractor::FactExtractor> =
|
let fact_extractor: Arc<dyn mem_ingest::fact_extractor::FactExtractor> =
|
||||||
Arc::new(SimpleFactExtractor);
|
Arc::new(SimpleFactExtractor);
|
||||||
let contradiction_detector = Arc::new(ContradictionHandler::default());
|
let contradiction_detector = Arc::new(ContradictionHandler::default());
|
||||||
|
|||||||
@@ -22,11 +22,15 @@ use tokio::sync::Mutex;
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ExtractedEntity {
|
pub struct ExtractedEntity {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
#[serde(alias = "type")]
|
||||||
pub entity_type: EntityType,
|
pub entity_type: EntityType,
|
||||||
pub summary: String,
|
pub summary: String,
|
||||||
|
#[serde(default = "default_confidence")]
|
||||||
pub confidence: f32,
|
pub confidence: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_confidence() -> f32 { 0.8 }
|
||||||
|
|
||||||
impl ExtractedEntity {
|
impl ExtractedEntity {
|
||||||
/// Convert to domain model (Phase 1 type)
|
/// Convert to domain model (Phase 1 type)
|
||||||
pub fn to_domain(&self, project_id: &str) -> Entity {
|
pub fn to_domain(&self, project_id: &str) -> Entity {
|
||||||
@@ -62,6 +66,25 @@ impl LlmEntityExtractor {
|
|||||||
|
|
||||||
/// Parse extraction response JSON
|
/// Parse extraction response JSON
|
||||||
/// Format: { "entities": [{ "name": "...", "type": "...", "summary": "..." }, ...] }
|
/// Format: { "entities": [{ "name": "...", "type": "...", "summary": "..." }, ...] }
|
||||||
|
/// Strip <think>...</think> tags from reasoning model output and extract JSON
|
||||||
|
fn strip_thinking_tags(text: &str) -> String {
|
||||||
|
let mut result = text.to_string();
|
||||||
|
// Remove <think>...</think> blocks
|
||||||
|
if let Some(start) = result.find("<think>") {
|
||||||
|
if let Some(end) = result.find("</think>") {
|
||||||
|
result = format!("{}{}", &result[..start], &result[end + 8..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try to find JSON object in remaining text
|
||||||
|
let trimmed = result.trim();
|
||||||
|
if let Some(start) = trimmed.find('{') {
|
||||||
|
if let Some(end) = trimmed.rfind('}') {
|
||||||
|
return trimmed[start..=end].to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trimmed.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_extraction(response: &str) -> Result<Vec<ExtractedEntity>> {
|
fn parse_extraction(response: &str) -> Result<Vec<ExtractedEntity>> {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Response {
|
struct Response {
|
||||||
@@ -146,12 +169,16 @@ impl LlmEntityExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let data: serde_json::Value = response.json().await?;
|
let data: serde_json::Value = response.json().await?;
|
||||||
let content = data["choices"][0]["message"]["content"]
|
let raw_content = data["choices"][0]["message"]["content"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.unwrap_or("{}")
|
.unwrap_or("{}")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
tracing::debug!("LLM response (via Authentik JWT): {}", content);
|
// Strip <think>...</think> tags from reasoning models
|
||||||
|
let content = Self::strip_thinking_tags(&raw_content);
|
||||||
|
|
||||||
|
tracing::debug!("LLM raw response length={}, cleaned length={}", raw_content.len(), content.len());
|
||||||
|
tracing::debug!("LLM cleaned content: {}", content);
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,14 +260,27 @@ Respond in JSON:
|
|||||||
);
|
);
|
||||||
|
|
||||||
let reflection = if std::env::var("LLM_ENDPOINT").is_ok() {
|
let reflection = if std::env::var("LLM_ENDPOINT").is_ok() {
|
||||||
self.call_llm_endpoint(&reflection_prompt).await.unwrap_or_else(|_| self.simulate_llm(&reflection_prompt).unwrap_or_default())
|
self.call_llm_endpoint(&reflection_prompt).await.unwrap_or_else(|e| {
|
||||||
|
tracing::warn!("Reflection LLM call failed: {}, skipping verification", e);
|
||||||
|
String::new()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
self.simulate_llm(&reflection_prompt)?
|
self.simulate_llm(&reflection_prompt)?
|
||||||
};
|
};
|
||||||
let verified = Self::parse_reflection(&reflection)?;
|
|
||||||
|
|
||||||
// Filter: keep only entities marked present
|
// If reflection succeeded, filter entities; otherwise keep all
|
||||||
|
if !reflection.is_empty() {
|
||||||
|
match Self::parse_reflection(&reflection) {
|
||||||
|
Ok(verified) => {
|
||||||
entities.retain(|e| verified.iter().any(|(name, present)| name == &e.name && *present));
|
entities.retain(|e| verified.iter().any(|(name, present)| name == &e.name && *present));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Reflection parse failed: {}, keeping all entities", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::info!("Reflection skipped, keeping {} unverified entities", entities.len());
|
||||||
|
}
|
||||||
|
|
||||||
// Adjust confidence for reflected entities (slight penalty for needing verification)
|
// Adjust confidence for reflected entities (slight penalty for needing verification)
|
||||||
for entity in &mut entities {
|
for entity in &mut entities {
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ spec:
|
|||||||
name: poimen-memory-auth
|
name: poimen-memory-auth
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: poimen-memory-secrets
|
name: poimen-memory-secrets
|
||||||
|
command: ["/app/mem"]
|
||||||
args:
|
args:
|
||||||
- serve
|
- serve
|
||||||
- --port
|
- --port
|
||||||
|
|||||||
Reference in New Issue
Block a user