Wrapper de LangChain para Phi-4 con soporte nativo de Tool Calling y MCP
Project description
PhiChat
Wrapper de LangChain para Phi-4 (14B) y la familia Phi de Microsoft via Ollama, con soporte robusto y nativo de tool calling.
Phi-4 genera y reconoce tool calls y responses mediante etiquetas propietarias (<|tool_call|> y <|tool_response|>). PhiChat normaliza este protocolo automáticamente para que cualquier flujo LCEL, agente o grafo funcione de forma transparente.
Instalacion
Requiere Ollama corriendo localmente.
ollama pull phi4
Dependencias (recomendado con uv):
# Inicializar y sincronizar dependencias
uv sync
O via pip:
pip install langchain-ollama langchain-core langchain-mcp-adapters mcp pydantic nest-asyncio
Inicio rapido
from PhiChat import ChatPhi
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""Obtiene el clima de una ciudad."""
return f"Soleado en {city}, 25 grados."
llm = ChatPhi()
chain = llm.bind_tools([get_weather])
response = chain.invoke("Como esta el clima en Madrid?")
print(response.content)
print(response.tool_calls)
Modelos recomendados
| phi4 (default) | 10 GB | Estado del arte, excelente razonamiento y herramientas |
| phi4-mini | 4 GB | Más rápido, ideal para tareas simples |
| phi3.5-mini | 4 GB | Muy rápido, pero propenso a errores en herramientas |
Cambiar de modelo:
llm = ChatPhi(model="phi4:latest")
Estructura del paquete
PhiChat/
├── pyproject.toml # Configuración del proyecto
├── PhiChat/ # Código fuente (Paquete)
│ ├── __init__.py
│ ├── model.py
│ ├── tools.py
│ ├── parsers.py
│ └── constants/
└── test/ # Suite de pruebas
Compatibilidad LCEL
ChatPhi hereda de ChatOllama y es compatible con la interfaz estandar de LangChain.
Pipe estandar
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "Eres un asistente util."),
("human", "{input}")
])
chain = prompt | ChatPhi() | StrOutputParser()
result = chain.invoke({"input": "Hola"})
bind_tools (LCEL completo)
bind_tools devuelve _PhiBoundModel, un Runnable propio que:
- Inyecta el system prompt y las definiciones JSON de las herramientas.
- Normaliza las tool calls al campo estándar
tool_callsy filtra los tokens de control del stream. - Re-inyecta el historial (llamadas y respuestas) para evitar bucles agénticos.
- Soporta herramientas asíncronas de forma nativa (ideal para servidores MCP).
- Soporta
.invoke(),.stream(),.batch(),.ainvoke(),.astream().
llm = ChatPhi()
bound = llm.bind_tools([get_weather])
# invoke
response = bound.invoke("Clima en Roma?")
# stream
for chunk in bound.stream("Clima en Tokio?"):
print(chunk.content, end="")
# async
response = await bound.ainvoke("Clima en Paris?")
with_structured_output
from pydantic import BaseModel, Field
class Ciudad(BaseModel):
nombre: str = Field(description="Nombre de la ciudad")
pais: str = Field(description="Pais donde esta la ciudad")
structured = ChatPhi().with_structured_output(Ciudad)
result = structured.invoke("Capital de Francia")
# result es un dict con las llaves del schema
Tool Calling
Definir una herramienta (estandar LangChain)
from langchain_core.tools import tool
from pydantic import BaseModel, Field
class WeatherInput(BaseModel):
city: str = Field(description="Nombre de la ciudad")
@tool(args_schema=WeatherInput)
def get_weather(city: str) -> str:
"""Obtiene el clima actual de una ciudad.
Usa esta herramienta cuando el usuario pregunte por el clima o temperatura.
"""
return f"Clima en {city}: soleado, 25 grados."
Nota: La descripcion de la herramienta es lo mas importante para phi4-mini. Debe describir cuando usarla, no solo que hace.
Loop agentico con run_tool_loop
Para flujos que requieren multiples pasos (LLM -> tool -> LLM):
llm = ChatPhi()
response = llm.run_tool_loop(
messages=[{"role": "user", "content": "Clima en Madrid y en Paris?"}],
tools=[get_weather],
verbose=True, # imprime cada paso
max_iterations=5
)
print(response.content)
Crear herramientas con create_tool
from PhiChat import create_tool
def buscar_producto(nombre: str) -> str:
return f"Producto encontrado: {nombre}"
tool = create_tool(
func=buscar_producto,
description="Busca un producto en el catalogo. Usa cuando el usuario quiera encontrar un articulo."
)
Integracion con LangGraph
ChatPhi.bind_tools() devuelve un Runnable compatible con create_react_agent:
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
llm = ChatPhi(model="phi4:latest", temperature=0)
agent = create_react_agent(
model=llm,
tools=[get_weather],
checkpointer=MemorySaver()
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "Clima en Berlin?"}]},
config={"configurable": {"thread_id": "1"}}
)
print(result["messages"][-1].content)
Configuracion
| Parametro | Default | Descripcion |
|---|---|---|
model |
phi4-mini:latest |
Modelo Ollama a usar |
temperature |
0.7 |
Temperatura de muestreo |
num_ctx |
8192 |
Tokens de contexto. Necesario aumentar con muchas tools |
keep_alive |
0 |
Tiempo en segundos para mantener el modelo en memoria |
tool_system_suffix |
(interno) | Sufijo inyectado en el system prompt para activar tools |
llm = ChatPhi(
model="phi4:latest",
temperature=0,
num_ctx=16384,
keep_alive=300
)
Por que es necesario PhiChat
| Comportamiento | Solución en PhiChat |
|---|---|
| Error 400 en Ollama (Native Tooling) | Bypass nativo e inyección manual de schemas en prompt |
Tool calls en content en lugar de tool_calls |
parse_phi4_tool_calls() extrae y normaliza el JSON |
| Olvida su propio historial de herramientas | Re-inyección automática de `< |
| Resultados de herramientas ignorados | Inyección de etiquetas `< |
| Tokens `< | tool_call |
| Salida estructurada inconsistente | Override de with_structured_output con modo JSON |
| Herramientas MCP asíncronas | Soporte nativo para ejecución async en run_tool_loop |
Integración con MCP (Model Context Protocol)
PhiChat es totalmente compatible con servidores MCP a través de langchain-mcp-adapters:
from langchain_mcp_adapters.client import MultiServerMCPClient
from PhiChat import ChatPhi
async def main():
# Conectar a servidores MCP
client = MultiServerMCPClient({"my_server": {"transport": "stdio", "command": "python", "args": ["server.py"]}})
mcp_tools = await client.get_tools()
# Integrar con PhiChat
llm = ChatPhi(model="phi4")
agent = llm.bind_tools(mcp_tools)
# ¡Listo! Phi-4 usará las herramientas del servidor MCP
res = await agent.ainvoke("Usa la herramienta del servidor MCP para...")
Ingeniería y Mejores Prácticas
El proyecto PhiChat sigue estándares estrictos de ingeniería de software para garantizar la mantenibilidad y robustez:
- Estándares Python: Adherencia a PEP 8 (estilo), PEP 484 (tipado estático) y PEP 257 (documentación).
- Validación Continua: Es obligatorio que cualquier cambio pase la suite de pruebas completa (
uv run pytest) para asegurar la integridad del protocolo de etiquetas de Phi-4. - Tipado Estricto: Uso de
Type Hintsen todas las funciones y clases para facilitar el análisis estático. - Resiliencia: Implementación de patrones de reintento y manejo de excepciones específicas para interactuar con modelos locales de forma segura.
- Ciclo de Vida (SDLC): Proceso definido desde el análisis del impacto en el parsing hasta la validación formal de integración.
Testing
La suite de pruebas garantiza la integridad de los protocolos de comunicación y la ejecución agéntica.
# Ejecutar todos los tests
uv run pytest
# Ejecutar un test específico (ej. los parsers)
uv run pytest test/test_parsers.py
Cubre: Parsing nativo de protocolos Phi-4, chat simple, selección de herramientas, salida estructurada, streaming con filtrado de tokens y ejecución de herramientas asíncronas (MCP).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file phichat-0.1.0.tar.gz.
File metadata
- Download URL: phichat-0.1.0.tar.gz
- Upload date:
- Size: 20.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc9383af80e1e774f5a73f79c10aa0497af013ba18b0d775cbc640f2ebf74042
|
|
| MD5 |
020ed47a619cddcae8fdd0503963e3cd
|
|
| BLAKE2b-256 |
d64823fa24ed0e6d725afd176a3565bfa244cd96dea3b1658b62a8fee9c9d66c
|
Provenance
The following attestation bundles were made for phichat-0.1.0.tar.gz:
Publisher:
publish.yml on CarlosMaroRuiz/PhiChat
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
phichat-0.1.0.tar.gz -
Subject digest:
bc9383af80e1e774f5a73f79c10aa0497af013ba18b0d775cbc640f2ebf74042 - Sigstore transparency entry: 1482792779
- Sigstore integration time:
-
Permalink:
CarlosMaroRuiz/PhiChat@fdf7ad93df23c1294ba2f232f80311cf98335c99 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/CarlosMaroRuiz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fdf7ad93df23c1294ba2f232f80311cf98335c99 -
Trigger Event:
push
-
Statement type:
File details
Details for the file phichat-0.1.0-py3-none-any.whl.
File metadata
- Download URL: phichat-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b13ef64f285cc4fd53108c043382e9399a9c26ca6d9d978e1274940c8bedbdd
|
|
| MD5 |
9408e91a12cd5261a02e80ddadec45ec
|
|
| BLAKE2b-256 |
48de1b576523af4d7aff158296bcbbb0c5a3499a32ba91287a1e1abfdd6c3242
|
Provenance
The following attestation bundles were made for phichat-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on CarlosMaroRuiz/PhiChat
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
phichat-0.1.0-py3-none-any.whl -
Subject digest:
7b13ef64f285cc4fd53108c043382e9399a9c26ca6d9d978e1274940c8bedbdd - Sigstore transparency entry: 1482792907
- Sigstore integration time:
-
Permalink:
CarlosMaroRuiz/PhiChat@fdf7ad93df23c1294ba2f232f80311cf98335c99 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/CarlosMaroRuiz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fdf7ad93df23c1294ba2f232f80311cf98335c99 -
Trigger Event:
push
-
Statement type: