feat(projekt-matching): seven Langflow wrapper components

This commit is contained in:
tlg
2026-07-09 11:23:08 +02:00
parent d500b46abe
commit dc69c9a66b
7 changed files with 242 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
import sys
if "/app/langflow" not in sys.path:
sys.path.insert(0, "/app/langflow")
from langflow.custom import Component
from langflow.io import DataInput, MessageTextInput, Output, SecretStrInput
from langflow.schema import Data
from projektmatch import stages
from projektmatch.config import Cfg
class PMCrm(Component):
display_name = "PM 5 CRM"
description = "EspoCRM: Team, Firma, Kontakt, Verkaufschance + Verify"
inputs = [
DataInput(name="ctx", display_name="Context"),
MessageTextInput(name="espo_base", display_name="Espo Base",
value="PM_ESPO_BASE", load_from_db=True,
advanced=True),
SecretStrInput(name="espo_api_key", display_name="Espo API Key",
value="PM_ESPO_API_KEY", load_from_db=True,
advanced=True),
]
outputs = [Output(name="out", display_name="Context", method="build_out")]
def build_out(self) -> Data:
cfg = Cfg(espo_base=self.espo_base, espo_api_key=self.espo_api_key)
ctx = stages.run_stage("crm", stages.stage_crm,
dict(self.ctx.data), cfg)
self.status = ctx.get("status", "")
return Data(data=ctx)

View File

@@ -0,0 +1,33 @@
import sys
if "/app/langflow" not in sys.path:
sys.path.insert(0, "/app/langflow")
from langflow.custom import Component
from langflow.io import DataInput, MessageTextInput, Output
from langflow.schema import Data
from projektmatch import stages
from projektmatch.config import Cfg
class PMExtract(Component):
display_name = "PM 2 Extract"
description = "LLM 1: Anforderungen strukturiert extrahieren"
inputs = [
DataInput(name="ctx", display_name="Context"),
MessageTextInput(name="vllm_base", display_name="vLLM Base",
value="PM_VLLM_BASE", load_from_db=True,
advanced=True),
MessageTextInput(name="vllm_model", display_name="vLLM Model",
value="PM_VLLM_MODEL", load_from_db=True,
advanced=True),
]
outputs = [Output(name="out", display_name="Context", method="build_out")]
def build_out(self) -> Data:
cfg = Cfg(vllm_base=self.vllm_base, vllm_model=self.vllm_model)
ctx = stages.run_stage("extract", stages.stage_extract,
dict(self.ctx.data), cfg)
self.status = ctx.get("status", "")
return Data(data=ctx)

View File

@@ -0,0 +1,26 @@
import sys
if "/app/langflow" not in sys.path:
sys.path.insert(0, "/app/langflow")
import json
from langflow.custom import Component
from langflow.io import MessageTextInput, Output
from langflow.schema import Data
from projektmatch import stages
class PMFetch(Component):
display_name = "PM 1 Fetch"
description = "Projektseite abrufen (canonical URL -> Seitentext)"
inputs = [MessageTextInput(name="payload", display_name="Payload JSON")]
outputs = [Output(name="out", display_name="Context", method="build_out")]
def build_out(self) -> Data:
ctx = json.loads(self.payload)
ctx.setdefault("status", "ok")
ctx = stages.run_stage("fetch", stages.stage_fetch, ctx, None)
self.status = ctx.get("status", "")
return Data(data=ctx)

View File

@@ -0,0 +1,52 @@
import sys
if "/app/langflow" not in sys.path:
sys.path.insert(0, "/app/langflow")
import json
from langflow.custom import Component
from langflow.io import DataInput, MessageTextInput, Output, SecretStrInput
from langflow.schema.message import Message
from projektmatch import ingest
from projektmatch.config import Cfg
class PMIngest(Component):
display_name = "PM Ingest"
description = ("IMAP-Postfach abrufen, Projekte splitten, CRM-Dedup, "
"Flow 2 je Projekt, Alert-Mail, Trigger-Mail -> Trash")
inputs = [
DataInput(name="trigger", display_name="Webhook Trigger"),
SecretStrInput(name="imap_password", display_name="Mail Password",
value="PM_IMAP_PASSWORD", load_from_db=True,
advanced=True),
MessageTextInput(name="espo_base", display_name="Espo Base",
value="PM_ESPO_BASE", load_from_db=True,
advanced=True),
SecretStrInput(name="espo_api_key", display_name="Espo API Key",
value="PM_ESPO_API_KEY", load_from_db=True,
advanced=True),
MessageTextInput(name="alert_to", display_name="Alert To",
value="PM_ALERT_TO", load_from_db=True,
advanced=True),
MessageTextInput(name="flow2_id", display_name="Flow 2 ID",
value="PM_FLOW2_ID", load_from_db=True,
advanced=True),
SecretStrInput(name="langflow_api_key", display_name="Langflow Key",
value="PM_LANGFLOW_API_KEY", load_from_db=True,
advanced=True),
]
outputs = [Output(name="out", display_name="Summary", method="build_out")]
def build_out(self) -> Message:
cfg = Cfg(imap_password=self.imap_password,
espo_base=self.espo_base,
espo_api_key=self.espo_api_key,
alert_to=self.alert_to,
flow2_id=self.flow2_id,
langflow_api_key=self.langflow_api_key)
summary = ingest.run_ingest(cfg)
self.status = json.dumps(summary)
return Message(text=json.dumps(summary))

View File

@@ -0,0 +1,33 @@
import sys
if "/app/langflow" not in sys.path:
sys.path.insert(0, "/app/langflow")
from langflow.custom import Component
from langflow.io import DataInput, MessageTextInput, Output
from langflow.schema import Data
from projektmatch import stages
from projektmatch.config import Cfg
class PMMatch(Component):
display_name = "PM 3 Match CV"
description = "LLM 2: Anforderungen gegen Lebenslauf bewerten"
inputs = [
DataInput(name="ctx", display_name="Context"),
MessageTextInput(name="vllm_base", display_name="vLLM Base",
value="PM_VLLM_BASE", load_from_db=True,
advanced=True),
MessageTextInput(name="vllm_model", display_name="vLLM Model",
value="PM_VLLM_MODEL", load_from_db=True,
advanced=True),
]
outputs = [Output(name="out", display_name="Context", method="build_out")]
def build_out(self) -> Data:
cfg = Cfg(vllm_base=self.vllm_base, vllm_model=self.vllm_model)
ctx = stages.run_stage("match", stages.stage_match,
dict(self.ctx.data), cfg)
self.status = ctx.get("status", "")
return Data(data=ctx)

View File

@@ -0,0 +1,34 @@
import sys
if "/app/langflow" not in sys.path:
sys.path.insert(0, "/app/langflow")
from langflow.custom import Component
from langflow.io import DataInput, MessageTextInput, Output, SecretStrInput
from langflow.schema.message import Message
from projektmatch import stages
from projektmatch.config import Cfg
class PMNotify(Component):
display_name = "PM 6 Notify"
description = "Benachrichtigungs-Mail bei created + Langfuse-Trace"
inputs = [
DataInput(name="ctx", display_name="Context"),
SecretStrInput(name="imap_password", display_name="Mail Password",
value="PM_IMAP_PASSWORD", load_from_db=True,
advanced=True),
MessageTextInput(name="notify_to", display_name="Notify To",
value="PM_NOTIFY_TO", load_from_db=True,
advanced=True),
]
outputs = [Output(name="out", display_name="Result", method="build_out")]
def build_out(self) -> Message:
cfg = Cfg(imap_password=self.imap_password, notify_to=self.notify_to)
# stage_notify is called DIRECTLY (not via run_stage): it must run for
# every terminal status so failed runs still post their Langfuse trace.
ctx = stages.stage_notify(dict(self.ctx.data), cfg)
self.status = ctx.get("status", "")
return Message(text=stages.summary(ctx))

View File

@@ -0,0 +1,31 @@
import sys
if "/app/langflow" not in sys.path:
sys.path.insert(0, "/app/langflow")
from langflow.custom import Component
from langflow.io import DataInput, MessageTextInput, Output
from langflow.schema import Data
from projektmatch import stages
from projektmatch.config import Cfg
class PMRules(Component):
display_name = "PM 4 Rules+Gate"
description = ("Deterministisch: Misc-Regeln, Match-Berechnung, "
"Beschreibungs-Markdown, Gate > Schwellwert")
inputs = [
DataInput(name="ctx", display_name="Context"),
MessageTextInput(name="threshold", display_name="Threshold",
value="PM_THRESHOLD", load_from_db=True,
advanced=True),
]
outputs = [Output(name="out", display_name="Context", method="build_out")]
def build_out(self) -> Data:
cfg = Cfg(threshold=int(self.threshold))
ctx = stages.run_stage("rules", stages.stage_rules,
dict(self.ctx.data), cfg)
self.status = f"{ctx.get('decision')} ({ctx.get('mustMatch')} %)"
return Data(data=ctx)