2026-06-15 15:06:48 +02:00
from __future__ import annotations
from datetime import datetime
import hashlib
import json
from typing import Any , Dict , List
from sqlalchemy . orm import Session
from . . models import WorkflowDefinition , WorkflowRevision
LIMITS = {
" maximum_nodes " : 40 ,
" maximum_tool_calls " : 20 ,
" maximum_llm_calls " : 8 ,
" maximum_runtime_seconds " : 300 ,
" maximum_result_chars " : 120000 ,
" maximum_concurrency " : 4 ,
}
INPUTS = { " prompt " : { " type " : " string " } , " session_id " : { " type " : [ " string " , " null " ] } }
def node ( node_id : str , node_type : str , x : float , y : float , config : Dict [ str , Any ] | None = None ) - > Dict [ str , Any ] :
return { " id " : node_id , " type " : node_type , " position " : { " x " : x , " y " : y } , " config " : config or { } }
def edge ( source : str , target : str , source_handle : str = " output " , suffix : str = " " ) - > Dict [ str , Any ] :
return {
" id " : f " { source } - { target } { suffix } " ,
" source " : source ,
" source_handle " : source_handle ,
" target " : target ,
" target_handle " : " input " ,
}
def chat_arguments ( context_blocks : List [ Dict [ str , str ] ] | None = None ) - > Dict [ str , Any ] :
return {
" model " : { " $ref " : " run.chat_model " } ,
" messages " : { " $ref " : " run.messages " } ,
" system_prompt " : " " ,
" context_blocks " : context_blocks or [ ] ,
" attachments " : { " $ref " : " run.attachments " } ,
" vision_model " : { " $ref " : " run.vision_model " } ,
" transcription_model " : { " $ref " : " run.transcription_model " } ,
" generation_options " : { " $ref " : " run.generation_options " } ,
" stream " : True ,
" reasoning " : False ,
}
def graph ( nodes : List [ Dict [ str , Any ] ] , edges : List [ Dict [ str , Any ] ] , * , limits : Dict [ str , Any ] | None = None ) - > Dict [ str , Any ] :
return {
" schema_version " : 1 ,
" inputs " : INPUTS ,
" outputs " : { " content " : { " type " : " string " } } ,
" nodes " : nodes ,
" edges " : edges ,
" limits " : { * * LIMITS , * * ( limits or { } ) } ,
}
DIRECT = graph (
[
node ( " input " , " input " , 0 , 80 ) ,
node ( " chat " , " tool " , 320 , 80 , { " tool " : " heimgeist.chat " , " arguments " : chat_arguments ( ) } ) ,
node ( " output " , " output " , 680 , 80 , { " value " : { " $ref " : " nodes.chat.output " } } ) ,
] ,
[ edge ( " input " , " chat " ) , edge ( " chat " , " output " ) ] ,
)
KNOWLEDGE = graph (
[
node ( " input " , " input " , 0 , 80 ) ,
node ( " search " , " tool " , 280 , 20 , { " tool " : " heimgeist.knowledge_search " , " arguments " : {
" prompt " : { " $ref " : " input.prompt " } , " library_slug " : { " $ref " : " run.library_slug " } ,
" top_k " : 6 , " context_character_budget " : 14000 , " embedding_model " : None ,
} } ) ,
node ( " chat " , " tool " , 620 , 80 , { " tool " : " heimgeist.chat " , " arguments " : chat_arguments ( [ { " $ref " : " nodes.search.output " } ] ) } ) ,
node ( " output " , " output " , 960 , 80 , { " value " : { " $ref " : " nodes.chat.output " } } ) ,
] ,
[ edge ( " input " , " search " ) , edge ( " search " , " chat " ) , edge ( " chat " , " output " ) ] ,
)
def web_graph ( ) - > Dict [ str , Any ] :
return graph (
[
node ( " input " , " input " , 0 , 100 ) ,
node ( " queries " , " tool " , 240 , 20 , { " tool " : " heimgeist.web_generate_queries " , " arguments " : {
" prompt " : { " $ref " : " input.prompt " } , " model " : { " $ref " : " run.chat_model " } , " messages " : { " $ref " : " run.messages " } ,
} } ) ,
node ( " search " , " tool " , 500 , 20 , { " tool " : " heimgeist.web_search " , " arguments " : {
" query " : None , " queries " : { " $ref " : " nodes.queries.output.queries " } , " engines " : { " $ref " : " run.searx_engines " } ,
" maximum_results " : 16 , " searx_url " : { " $ref " : " run.searx_url " } ,
} } ) ,
node ( " fetch " , " tool " , 760 , 20 , { " tool " : " heimgeist.web_fetch " , " arguments " : {
" url " : None , " urls " : { " $ref " : " nodes.search.output.results " } , " maximum_pages " : 6 ,
} } ) ,
node ( " rerank " , " tool " , 1010 , 20 , { " tool " : " heimgeist.web_rerank " , " arguments " : {
" prompt " : { " $ref " : " input.prompt " } , " pages " : { " $ref " : " nodes.fetch.output.pages " } , " model " : { " $ref " : " run.chat_model " } ,
" rerank_model " : None , " context_excerpt " : " " , " maximum_results " : 6 , " minimum_score " : 55 ,
} } ) ,
node ( " chat " , " tool " , 1260 , 100 , { " tool " : " heimgeist.chat " , " arguments " : chat_arguments ( [ { " $ref " : " nodes.rerank.output " } ] ) } ) ,
node ( " output " , " output " , 1540 , 100 , { " value " : { " $ref " : " nodes.chat.output " } } ) ,
] ,
[ edge ( " input " , " queries " ) , edge ( " queries " , " search " ) , edge ( " search " , " fetch " ) , edge ( " fetch " , " rerank " ) , edge ( " rerank " , " chat " ) , edge ( " chat " , " output " ) ] ,
)
VISION = graph (
[
node ( " input " , " input " , 0 , 80 ) ,
node ( " vision " , " tool " , 280 , 20 , { " tool " : " heimgeist.vision_analyze " , " arguments " : {
" attachments " : { " $ref " : " run.attachments " } , " vision_model " : { " $ref " : " run.vision_model " } ,
" transcription_model " : { " $ref " : " run.transcription_model " } ,
} } ) ,
node ( " chat " , " tool " , 620 , 80 , { " tool " : " heimgeist.chat " , " arguments " : chat_arguments ( [ { " $ref " : " nodes.vision.output " } ] ) } ) ,
node ( " output " , " output " , 960 , 80 , { " value " : { " $ref " : " nodes.chat.output " } } ) ,
] ,
[ edge ( " input " , " vision " ) , edge ( " vision " , " chat " ) , edge ( " chat " , " output " ) ] ,
limits = { " maximum_runtime_seconds " : 600 } ,
)
KNOWLEDGE_WEB = graph (
[
node ( " input " , " input " , 0 , 150 ) ,
node ( " knowledge " , " tool " , 260 , 20 , { " tool " : " heimgeist.knowledge_search " , " arguments " : {
" prompt " : { " $ref " : " input.prompt " } , " library_slug " : { " $ref " : " run.library_slug " } , " top_k " : 5 ,
" context_character_budget " : 10000 , " embedding_model " : None ,
} } ) ,
node ( " queries " , " tool " , 260 , 240 , { " tool " : " heimgeist.web_generate_queries " , " arguments " : {
" prompt " : { " $ref " : " input.prompt " } , " model " : { " $ref " : " run.chat_model " } , " messages " : { " $ref " : " run.messages " } ,
} } ) ,
node ( " search " , " tool " , 520 , 240 , { " tool " : " heimgeist.web_search " , " arguments " : {
" query " : None , " queries " : { " $ref " : " nodes.queries.output.queries " } , " engines " : { " $ref " : " run.searx_engines " } ,
" maximum_results " : 12 , " searx_url " : { " $ref " : " run.searx_url " } ,
} } ) ,
node ( " fetch " , " tool " , 780 , 240 , { " tool " : " heimgeist.web_fetch " , " arguments " : { " url " : None , " urls " : { " $ref " : " nodes.search.output.results " } , " maximum_pages " : 5 } } ) ,
node ( " rerank " , " tool " , 1040 , 240 , { " tool " : " heimgeist.web_rerank " , " arguments " : {
" prompt " : { " $ref " : " input.prompt " } , " pages " : { " $ref " : " nodes.fetch.output.pages " } , " model " : { " $ref " : " run.chat_model " } ,
" rerank_model " : None , " context_excerpt " : " " , " maximum_results " : 5 , " minimum_score " : 50 ,
} } ) ,
node ( " merge " , " merge " , 1300 , 130 , { " values " : [ { " $ref " : " nodes.knowledge.output " } , { " $ref " : " nodes.rerank.output " } ] , " deduplicate_by " : " url " } ) ,
node ( " limit " , " limit " , 1520 , 130 , { " value " : { " $ref " : " nodes.merge.output " } , " maximum_chars " : 22000 } ) ,
node ( " chat " , " tool " , 1760 , 130 , { " tool " : " heimgeist.chat " , " arguments " : chat_arguments ( [ { " $ref " : " nodes.limit.output " } ] ) } ) ,
node ( " output " , " output " , 2040 , 130 , { " value " : { " $ref " : " nodes.chat.output " } } ) ,
] ,
[
edge ( " input " , " knowledge " ) , edge ( " input " , " queries " ) , edge ( " queries " , " search " ) , edge ( " search " , " fetch " ) , edge ( " fetch " , " rerank " ) ,
edge ( " knowledge " , " merge " ) , edge ( " rerank " , " merge " ) , edge ( " merge " , " limit " ) , edge ( " limit " , " chat " ) , edge ( " chat " , " output " ) ,
] ,
)
REMEMBER = graph (
[
node ( " input " , " input " , 0 , 80 ) ,
node ( " save " , " tool " , 300 , 80 , { " tool " : " heimgeist.save_message_to_knowledge " , " arguments " : {
2026-06-15 20:26:12 +02:00
" message_id " : { " $ref " : " run.target_message_id " } , " library_slug " : { " $ref " : " run.library_slug " } , " title " : None , " edited_content " : { " $ref " : " run.target_message_content " } ,
2026-06-15 15:06:48 +02:00
} } ) ,
2026-06-15 15:37:43 +02:00
node ( " confirmed " , " condition " , 570 , 80 , { " value " : { " $ref " : " nodes.save.output.confirmed " } , " operation " : " equals " , " compare_to " : True } ) ,
node ( " saved " , " template " , 830 , 20 , { " template " : " Saved the selected message to knowledge. " } ) ,
node ( " rejected " , " template " , 830 , 150 , { " template " : " The knowledge save was cancelled. " } ) ,
node ( " response " , " merge " , 1080 , 80 , { " values " : [ { " $ref " : " nodes.saved.output " } , { " $ref " : " nodes.rejected.output " } ] , " allow_missing " : True } ) ,
node ( " output " , " output " , 1320 , 80 , { " value " : { " content " : { " $ref " : " nodes.response.output.context_block " } , " sources " : [ ] , " usage " : { } } } ) ,
] ,
[
edge ( " input " , " save " ) , edge ( " save " , " confirmed " ) ,
edge ( " confirmed " , " saved " , " true " ) , edge ( " confirmed " , " rejected " , " false " ) ,
edge ( " saved " , " response " ) , edge ( " rejected " , " response " ) , edge ( " response " , " output " ) ,
2026-06-15 15:06:48 +02:00
] ,
)
SAVE_SOURCE = graph (
[
node ( " input " , " input " , 0 , 80 ) ,
node ( " save " , " tool " , 300 , 80 , { " tool " : " heimgeist.save_website_to_knowledge " , " arguments " : {
" url " : { " $ref " : " run.target_url " } , " library_slug " : { " $ref " : " run.library_slug " } , " title " : None ,
} } ) ,
2026-06-15 15:37:43 +02:00
node ( " confirmed " , " condition " , 570 , 80 , { " value " : { " $ref " : " nodes.save.output.confirmed " } , " operation " : " equals " , " compare_to " : True } ) ,
node ( " saved " , " template " , 830 , 20 , { " template " : " Saved the website snapshot to knowledge. " } ) ,
node ( " rejected " , " template " , 830 , 150 , { " template " : " The website save was cancelled. " } ) ,
node ( " response " , " merge " , 1080 , 80 , { " values " : [ { " $ref " : " nodes.saved.output " } , { " $ref " : " nodes.rejected.output " } ] , " allow_missing " : True } ) ,
node ( " output " , " output " , 1320 , 80 , { " value " : { " content " : { " $ref " : " nodes.response.output.context_block " } , " sources " : [ ] , " usage " : { } } } ) ,
] ,
[
edge ( " input " , " save " ) , edge ( " save " , " confirmed " ) ,
edge ( " confirmed " , " saved " , " true " ) , edge ( " confirmed " , " rejected " , " false " ) ,
edge ( " saved " , " response " ) , edge ( " rejected " , " response " ) , edge ( " response " , " output " ) ,
2026-06-15 15:06:48 +02:00
] ,
)
RESEARCH = graph (
[
node ( " input " , " input " , 0 , 180 ) ,
node ( " analyze " , " prompt " , 220 , 180 , {
" model_source " : " chat_model " , " system_template " : " Extract the core research question. " ,
" user_template " : " {{ input.prompt}} " , " output_mode " : " text " , " temperature " : 0.1 ,
} ) ,
node ( " queries1 " , " tool " , 470 , 80 , { " tool " : " heimgeist.web_generate_queries " , " arguments " : {
" prompt " : { " $ref " : " nodes.analyze.output.content " } , " model " : { " $ref " : " run.chat_model " } , " messages " : { " $ref " : " run.messages " } ,
} } ) ,
node ( " search1 " , " tool " , 710 , 80 , { " tool " : " heimgeist.web_search " , " arguments " : {
" query " : None , " queries " : { " $ref " : " nodes.queries1.output.queries " } , " engines " : { " $ref " : " run.searx_engines " } ,
" maximum_results " : 18 , " searx_url " : { " $ref " : " run.searx_url " } ,
} } ) ,
node ( " fetch1 " , " tool " , 950 , 80 , { " tool " : " heimgeist.web_fetch " , " arguments " : { " url " : None , " urls " : { " $ref " : " nodes.search1.output.results " } , " maximum_pages " : 7 } } ) ,
node ( " rank1 " , " tool " , 1190 , 80 , { " tool " : " heimgeist.web_rerank " , " arguments " : {
" prompt " : { " $ref " : " input.prompt " } , " pages " : { " $ref " : " nodes.fetch1.output.pages " } , " model " : { " $ref " : " run.chat_model " } ,
" rerank_model " : None , " context_excerpt " : " " , " maximum_results " : 6 , " minimum_score " : 50 ,
} } ) ,
node ( " evaluate " , " prompt " , 1430 , 80 , {
" model_source " : " chat_model " , " system_template " : " Judge whether the evidence can answer the request. Return JSON. " ,
" user_template " : " Request: {{ input.prompt}} \n Evidence: {{ nodes.rank1.output.context_block}} " ,
" output_mode " : " json " , " json_schema " : { " type " : " object " , " properties " : { " sufficient " : { " type " : " boolean " } , " follow_up_query " : { " type " : " string " } } , " required " : [ " sufficient " , " follow_up_query " ] , " additionalProperties " : False } ,
" temperature " : 0.1 , " json_repair " : True ,
} ) ,
node ( " sufficient " , " condition " , 1690 , 80 , { " value " : { " $ref " : " nodes.evaluate.output.sufficient " } , " operation " : " equals " , " compare_to " : True } ) ,
node ( " queries2 " , " select " , 1910 , 220 , { " value " : { " $ref " : " nodes.evaluate.output.follow_up_query " } } ) ,
node ( " search2 " , " tool " , 2130 , 220 , { " tool " : " heimgeist.web_search " , " arguments " : {
" query " : { " $ref " : " nodes.queries2.output " } , " queries " : [ ] , " engines " : { " $ref " : " run.searx_engines " } ,
" maximum_results " : 10 , " searx_url " : { " $ref " : " run.searx_url " } ,
} } ) ,
node ( " fetch2 " , " tool " , 2350 , 220 , { " tool " : " heimgeist.web_fetch " , " arguments " : { " url " : None , " urls " : { " $ref " : " nodes.search2.output.results " } , " maximum_pages " : 4 } } ) ,
node ( " rank2 " , " tool " , 2570 , 220 , { " tool " : " heimgeist.web_rerank " , " arguments " : {
" prompt " : { " $ref " : " input.prompt " } , " pages " : { " $ref " : " nodes.fetch2.output.pages " } , " model " : { " $ref " : " run.chat_model " } ,
" rerank_model " : None , " context_excerpt " : " " , " maximum_results " : 4 , " minimum_score " : 45 ,
} } ) ,
node ( " empty2 " , " template " , 1910 , - 80 , { " template " : " " , " values " : { } } ) ,
node ( " merge " , " merge " , 2820 , 80 , { " values " : [ { " $ref " : " nodes.rank1.output " } , { " $ref " : " nodes.rank2.output " } ] , " deduplicate_by " : " url " , " allow_missing " : True } ) ,
node ( " limit " , " limit " , 3050 , 80 , { " value " : { " $ref " : " nodes.merge.output " } , " maximum_chars " : 28000 } ) ,
node ( " chat " , " tool " , 3280 , 80 , { " tool " : " heimgeist.chat " , " arguments " : chat_arguments ( [ { " $ref " : " nodes.limit.output " } ] ) } ) ,
node ( " output " , " output " , 3520 , 80 , { " value " : { " $ref " : " nodes.chat.output " } } ) ,
] ,
[
edge ( " input " , " analyze " ) , edge ( " analyze " , " queries1 " ) , edge ( " queries1 " , " search1 " ) , edge ( " search1 " , " fetch1 " ) , edge ( " fetch1 " , " rank1 " ) ,
edge ( " rank1 " , " evaluate " ) , edge ( " evaluate " , " sufficient " ) , edge ( " sufficient " , " empty2 " , " true " , " -true " ) , edge ( " sufficient " , " queries2 " , " false " , " -false " ) ,
edge ( " queries2 " , " search2 " ) , edge ( " search2 " , " fetch2 " ) , edge ( " fetch2 " , " rank2 " ) , edge ( " rank1 " , " merge " ) , edge ( " rank2 " , " merge " ) , edge ( " empty2 " , " merge " ) ,
edge ( " merge " , " limit " ) , edge ( " limit " , " chat " ) , edge ( " chat " , " output " ) ,
] ,
limits = { " maximum_tool_calls " : 24 , " maximum_llm_calls " : 10 , " maximum_runtime_seconds " : 420 } ,
)
BUILTIN_WORKFLOWS = [
{ " slug " : " input-output " , " name " : " Input -> Output " , " description " : " Normal Heimgeist chat with optional compatibility context. " , " routing_description " : " Use for ordinary conversation and requests that need no special retrieval. " , " routing_examples " : [ " Explain this concept " , " Draft a reply " ] , " estimated_cost_class " : " low " , " required_capabilities " : [ " chat " ] , " graph " : DIRECT } ,
{ " slug " : " knowledge-answer " , " name " : " Knowledge Answer " , " description " : " Answer from a selected local knowledge database. " , " routing_description " : " Use when the user asks about information in the selected local database. " , " routing_examples " : [ " What do my notes say about this? " ] , " estimated_cost_class " : " medium " , " required_capabilities " : [ " rag " ] , " graph " : KNOWLEDGE } ,
{ " slug " : " web-answer " , " name " : " Web Answer " , " description " : " Search, fetch, rank, and answer from current web sources. " , " routing_description " : " Use for current information or explicit requests to search the web. " , " routing_examples " : [ " What happened today? " , " Look this up online " ] , " estimated_cost_class " : " medium " , " required_capabilities " : [ " web " ] , " graph " : web_graph ( ) } ,
{ " slug " : " vision-answer " , " name " : " Vision Answer " , " description " : " Analyze attachments and answer with a vision model. " , " routing_description " : " Use when image or document attachments must be analyzed. " , " routing_examples " : [ " What is shown in this image? " ] , " estimated_cost_class " : " medium " , " required_capabilities " : [ " vision " ] , " graph " : VISION } ,
{ " slug " : " knowledge-web-answer " , " name " : " Knowledge + Web Answer " , " description " : " Combine local knowledge and current web evidence. " , " routing_description " : " Use when both the selected database and current web information are relevant. " , " routing_examples " : [ " Compare my notes with the latest information " ] , " estimated_cost_class " : " high " , " required_capabilities " : [ " rag " , " web " ] , " graph " : KNOWLEDGE_WEB } ,
{ " slug " : " remember-this " , " name " : " Remember This " , " description " : " Save a selected chat message to knowledge. " , " routing_description " : " Use only when the user explicitly asks to remember or save a chat message. " , " routing_examples " : [ " Remember this answer " ] , " estimated_cost_class " : " low " , " required_capabilities " : [ " knowledge_write " ] , " graph " : REMEMBER } ,
{ " slug " : " save-source " , " name " : " Save Source " , " description " : " Save a website snapshot to knowledge. " , " routing_description " : " Use when the user explicitly asks to save a URL as a knowledge source. " , " routing_examples " : [ " Save this website to my database " ] , " estimated_cost_class " : " low " , " required_capabilities " : [ " web " , " knowledge_write " ] , " graph " : SAVE_SOURCE } ,
{ " slug " : " research " , " name " : " Research " , " description " : " Bounded two-round evidence research with source validation. " , " routing_description " : " Use for multi-source research questions requiring deeper evidence gathering. " , " routing_examples " : [ " Research the competing explanations and cite sources " ] , " estimated_cost_class " : " high " , " required_capabilities " : [ " web " , " chat " ] , " graph " : RESEARCH } ,
]
def _checksum ( graph_value : Dict [ str , Any ] ) - > str :
canonical = json . dumps ( graph_value , sort_keys = True , separators = ( " , " , " : " ) , ensure_ascii = False )
return hashlib . sha256 ( canonical . encode ( " utf-8 " ) ) . hexdigest ( )
def seed_builtin_workflows ( session : Session ) - > None :
for item in BUILTIN_WORKFLOWS :
workflow = session . query ( WorkflowDefinition ) . filter ( WorkflowDefinition . slug == item [ " slug " ] ) . first ( )
if workflow is None :
workflow = WorkflowDefinition (
slug = item [ " slug " ] , name = item [ " name " ] , description = item [ " description " ] , built_in = True , enabled = True ,
routing_description = item [ " routing_description " ] , routing_examples_json = json . dumps ( item [ " routing_examples " ] ) ,
estimated_cost_class = item [ " estimated_cost_class " ] , required_capabilities_json = json . dumps ( item [ " required_capabilities " ] ) ,
)
session . add ( workflow )
session . flush ( )
else :
workflow . name = item [ " name " ]
workflow . description = item [ " description " ]
workflow . built_in = True
workflow . routing_description = item [ " routing_description " ]
workflow . routing_examples_json = json . dumps ( item [ " routing_examples " ] )
workflow . estimated_cost_class = item [ " estimated_cost_class " ]
workflow . required_capabilities_json = json . dumps ( item [ " required_capabilities " ] )
workflow . updated_at = datetime . utcnow ( )
checksum = _checksum ( item [ " graph " ] )
revision = session . query ( WorkflowRevision ) . filter (
WorkflowRevision . workflow_id == workflow . id ,
WorkflowRevision . checksum == checksum ,
) . first ( )
if revision is None :
latest = session . query ( WorkflowRevision ) . filter ( WorkflowRevision . workflow_id == workflow . id ) . order_by ( WorkflowRevision . version . desc ( ) ) . first ( )
revision = WorkflowRevision (
workflow_id = workflow . id ,
version = ( latest . version + 1 ) if latest else 1 ,
graph_json = json . dumps ( item [ " graph " ] , ensure_ascii = False ) ,
created_by = " system " ,
trusted = True ,
checksum = checksum ,
)
session . add ( revision )
session . flush ( )
workflow . current_revision_id = revision . id
session . commit ( )