50 lines
1.7 KiB
Python
50 lines
1.7 KiB
Python
from typing import Any, Optional
|
|
|
|
from ai_prompter import Prompter
|
|
from langchain_core.messages import HumanMessage, SystemMessage
|
|
from langchain_core.runnables import RunnableConfig
|
|
from langgraph.graph import END, START, StateGraph
|
|
from typing_extensions import TypedDict
|
|
|
|
from open_notebook.ai.provision import provision_langchain_model
|
|
from open_notebook.utils.text_utils import clean_thinking_content, extract_text_content
|
|
|
|
|
|
class PatternChainState(TypedDict):
|
|
prompt: str
|
|
parser: Optional[Any]
|
|
input_text: str
|
|
output: str
|
|
|
|
|
|
async def call_model(state: dict, config: RunnableConfig) -> dict:
|
|
content = state["input_text"]
|
|
# state["prompt"] is caller-supplied free text. Never compile it as Jinja
|
|
# template *source* (Prompter(template_text=...)) - pass it as a plain
|
|
# render variable into a fixed, developer-authored template instead.
|
|
# See docs/7-DEVELOPMENT/security.md (GHSA-f35w-wx37-26q7).
|
|
system_prompt = Prompter(
|
|
prompt_template="pattern/generic", parser=state.get("parser")
|
|
).render(data=state)
|
|
payload = [SystemMessage(content=system_prompt)] + [HumanMessage(content=content)]
|
|
chain = await provision_langchain_model(
|
|
str(payload),
|
|
config.get("configurable", {}).get("model_id"),
|
|
"transformation",
|
|
max_tokens=5000,
|
|
)
|
|
|
|
response = await chain.ainvoke(payload)
|
|
|
|
# Clean thinking tags from response (handles extended thinking models)
|
|
output = clean_thinking_content(extract_text_content(response.content))
|
|
return {"output": output}
|
|
|
|
|
|
agent_state = StateGraph(PatternChainState)
|
|
agent_state.add_node("agent", call_model) # type: ignore[type-var]
|
|
agent_state.add_edge(START, "agent")
|
|
agent_state.add_edge("agent", END)
|
|
|
|
graph = agent_state.compile()
|