| from dotenv import load_dotenv |
| from langchain_openai import ChatOpenAI |
| from pydantic import BaseModel, Field |
| from trustcall import create_extractor |
| from langchain_core.messages import HumanMessage, SystemMessage, AIMessage |
| from typing import TypedDict, Literal |
|
|
| load_dotenv() |
| model = ChatOpenAI(model="gpt-4.1-mini", temperature=0) |
|
|
| class Memory(BaseModel): |
| content: str = Field(description="The main content of the memory. For example: User expressed interest in learning about French.") |
|
|
| class MemoryCollection(BaseModel): |
| memories: list[Memory] = Field(description="A list of memories about the user.") |
|
|
| |
| class Spy: |
| def __init__(self): |
| self.called_tools = [] |
|
|
| def __call__(self, run): |
| |
| q = [run] |
| while q: |
| r = q.pop() |
| if r.child_runs: |
| q.extend(r.child_runs) |
| if r.run_type == "chat_model": |
| self.called_tools.append( |
| r.outputs["generations"][0][0]["message"]["kwargs"]["tool_calls"] |
| ) |
|
|
| |
| spy = Spy() |
|
|
| |
| trustcall_extractor = create_extractor( |
| model, |
| tools=[Memory], |
| tool_choice="Memory", |
| enable_inserts=True, |
| ) |
|
|
| |
| trustcall_extractor_see_all_tool_calls = trustcall_extractor.with_listeners(on_end=spy) |
|
|
| |
| instruction = """Extract memories from the following conversation:""" |
|
|
| |
| conversation = [HumanMessage(content="Hi, I'm Lance."), |
| AIMessage(content="Nice to meet you, Lance."), |
| HumanMessage(content="This morning I had a nice bike ride in San Francisco.")] |
|
|
| |
| result = trustcall_extractor.invoke({"messages": [SystemMessage(content=instruction)] + conversation}) |
|
|
| print("------------------") |
| print("Mensaje: 1") |
| print("------------------") |
| |
| for m in result["messages"]: |
| m.pretty_print() |
|
|
| |
| updated_conversation = [AIMessage(content="That's great, did you do after?"), |
| HumanMessage(content="I went to Tartine and ate a croissant."), |
| AIMessage(content="What else is on your mind?"), |
| HumanMessage(content="I was thinking about my Japan, and going back this winter!"),] |
|
|
|
|
| print("------------------") |
| print("Mensaje: 2: Update system message") |
| print("------------------") |
| |
| system_msg = """Update existing memories and create new ones based on the following conversation:""" |
|
|
| |
| tool_name = "Memory" |
| existing_memories = [(str(i), tool_name, memory.model_dump()) for i, memory in enumerate(result["responses"])] if result["responses"] else None |
| print(existing_memories) |
|
|
| |
| result = trustcall_extractor_see_all_tool_calls.invoke({"messages": updated_conversation, |
| "existing": existing_memories}) |
|
|
| print("------------------") |
| print("Mensaje: 3: metadata and tool calls") |
| print("------------------") |
| |
| for m in result["response_metadata"]: |
| print(m) |
|
|
| print("------------------") |
| print("Mensaje: 4: metadata and tool calls") |
| print("------------------") |
| |
| for m in result["messages"]: |
| m.pretty_print() |
|
|
| print("------------------") |
| print("Mensaje: 5: Parsed responses") |
| print("------------------") |
| |
| for m in result["responses"]: |
| print(m) |
|
|
| print("------------------") |
| print("Mensaje: 6: Inspect the tool calls made by Trustcall") |
| print("------------------") |
| |
| print(spy.called_tools) |
|
|
| def extract_tool_info(tool_calls, schema_name="Memory"): |
| """Extract information from tool calls for both patches and new memories. |
| |
| Args: |
| tool_calls: List of tool calls from the model |
| schema_name: Name of the schema tool (e.g., "Memory", "ToDo", "Profile") |
| """ |
|
|
| |
| changes = [] |
| |
| for call_group in tool_calls: |
| for call in call_group: |
| if call['name'] == 'PatchDoc': |
| changes.append({ |
| 'type': 'update', |
| 'doc_id': call['args']['json_doc_id'], |
| 'planned_edits': call['args']['planned_edits'], |
| 'value': call['args']['patches'][0]['value'] |
| }) |
| elif call['name'] == schema_name: |
| changes.append({ |
| 'type': 'new', |
| 'value': call['args'] |
| }) |
|
|
| |
| result_parts = [] |
| for change in changes: |
| if change['type'] == 'update': |
| result_parts.append( |
| f"Document {change['doc_id']} updated:\n" |
| f"Plan: {change['planned_edits']}\n" |
| f"Added content: {change['value']}" |
| ) |
| else: |
| result_parts.append( |
| f"New {schema_name} created:\n" |
| f"Content: {change['value']}" |
| ) |
| |
| return "\n\n".join(result_parts) |
|
|
| print("------------------") |
| print("Mensaje: 7: Extracted changes") |
| print("------------------") |
| |
| schema_name = "Memory" |
| changes = extract_tool_info(spy.called_tools, schema_name) |
| print(changes) |