Django OpenAI Integration Error
2024-12-0220 turns172,506 charsgpt-4o, o1-mini, o1-preview
Summary
User debugging Django OpenAI integration errors including LangChain deprecation warnings and JavaScript undefined property issues.
Messages
Django version 5.1.3, using settings 'backend.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
DEBUG:root:Request data: {'text': '\nThe family, I repeat, was now united for the first time, and some of\nits members met for the first time in their lives. The younger brother,\nAlexey, had been a year already among us, having been the first of the\nthree to arrive. It is of that brother Alexey I find it most difficult\nto speak in this introduction. Yet I must give some preliminary account\nof him, if only to explain one queer fact, which is that I have to\nintroduce my hero to the reader wearing the cassock of a novice. Yes,\nhe had been for the last year in our monastery, and seemed willing to\nbe cloistered there for the rest of his life.\n\n\n\n\nChapter IV.\nThe Third Son, Alyosha\n\n\nHe was only twenty, his brother Ivan was in his twenty‐fourth year at\nthe time, while their elder brother Dmitri was twenty‐seven. First of\nall, I must explain that this young man, Alyosha, was not a fanatic,\nand, in my opinion at least, was not even a mystic. I may as well give\nmy full opinion from the beginning. He was simply an early lover of\nhumanity, and that he adopted the monastic life was simply because at\nthat time it struck him, so to say, as the ideal escape for his soul\nstruggling from the darkness of worldly wickedness to the light of\nlove. And the reason this life struck him in this way was that he found\nin it at that time, as he thought, an extraordinary being, our\ncelebrated elder, Zossima, to whom he became attached with all the warm\nfirst love of his ardent heart. But I do not dispute that he was very\nstrange even at that time, and had been so indeed from his cradle. I\nhave mentioned already, by the way, that though he lost his mother in\nhis fourth year he remembered her all his life—her face, her caresses,\n“as though she stood living before me.” Such memories may persist, as\nevery one knows, from an even earlier age, even from two years old, but\nscarcely standing out through a whole lifetime like spots of light out\nof darkness, like a corner torn out of a huge picture, which has all\nfaded and disappeared except that fragment. That is how it was with\nhim. He remembered one still summer evening, an open window, the\nslanting rays of the setting sun (that he recalled most vividly of\nall); in a corner of the room the holy image, before it a lighted lamp,\nand on her knees before the image his mother, sobbing hysterically with\ncries and moans, snatching him up in both arms, squeezing him close\ntill it hurt, and praying for him to the Mother of God, holding him out\nin both arms to the image as though to put him under the Mother’s\nprotection ... and suddenly a nurse runs in and snatches him from her\nin terror. That was the picture! And Alyosha remembered his mother’s\nface at that minute. He used to say that it was frenzied but beautiful\nas he remembered. But he rarely cared to speak of this memory to any\none. In his childhood and youth he was by no means expansive, and\ntalked little indeed, but not from shyness or a sullen unsociability;\nquite the contrary, from something different, from a sort of inner\npreoccupation entirely personal and unconcerned with other people, but\nso important to him that he seemed, as it were, to forget others on\naccount of it. But he was fond of people: he seemed throughout his life\nto put implicit trust in people: yet no one ever looked on him as a\nsimpleton or naïve person. There was something about him which made one\nfeel at once (and it was so all his life afterwards) that he did not\ncare to be a judge of others—that he would never take it upon himself\nto criticize and would never condemn any one for anything. He seemed,\nindeed, to accept everything without the least condemnation though\noften grieving bitterly: and this was so much so that no one could\nsurprise or frighten him even in his earliest youth. Coming at twenty\nto his father’s house, which was a very sink of filthy debauchery, he,\nchaste and pure as he was, simply withdrew in silence when to look on\nwas unbearable, but without the slightest sign of contempt or\ncondemnation. His father, who had once been in a dependent position,\nand so was sensitive and ready to take offense, met him at first with\ndistrust and sullenness. “He does not say much,” he used to say, “and\nthinks the more.” But soon, within a fortnight indeed, he took to\nembracing him and kissing him terribly often, with drunken tears, with\nsottish sentimentality, yet he evidently felt a real and deep affection\nfor him, such as he had never been capable of feeling for any one\nbefore.', 'documents': ['Document 1 content', 'Document 2 content']}
DEBUG:root:Input text:
The family, I repeat, was now united for the first time, and some of
its members met for the first time in their lives. The younger brother,
Alexey, had been a year already among us, having been the first of the
three to arrive. It is of that brother Alexey I find it most difficult
to speak in this introduction. Yet I must give some preliminary account
of him, if only to explain one queer fact, which is that I have to
introduce my hero to the reader wearing the cassock of a novice. Yes,
he had been for the last year in our monastery, and seemed willing to
be cloistered there for the rest of his life.
Chapter IV.
The Third Son, Alyosha
He was only twenty, his brother Ivan was in his twenty‐fourth year at
the time, while their elder brother Dmitri was twenty‐seven. First of
all, I must explain that this young man, Alyosha, was not a fanatic,
and, in my opinion at least, was not even a mystic. I may as well give
my full opinion from the beginning. He was simply an early lover of
humanity, and that he adopted the monastic life was simply because at
that time it struck him, so to say, as the ideal escape for his soul
struggling from the darkness of worldly wickedness to the light of
love. And the reason this life struck him in this way was that he found
in it at that time, as he thought, an extraordinary being, our
celebrated elder, Zossima, to whom he became attached with all the warm
first love of his ardent heart. But I do not dispute that he was very
strange even at that time, and had been so indeed from his cradle. I
have mentioned already, by the way, that though he lost his mother in
his fourth year he remembered her all his life—her face, her caresses,
“as though she stood living before me.” Such memories may persist, as
every one knows, from an even earlier age, even from two years old, but
scarcely standing out through a whole lifetime like spots of light out
of darkness, like a corner torn out of a huge picture, which has all
faded and disappeared except that fragment. That is how it was with
him. He remembered one still summer evening, an open window, the
slanting rays of the setting sun (that he recalled most vividly of
all); in a corner of the room the holy image, before it a lighted lamp,
and on her knees before the image his mother, sobbing hysterically with
cries and moans, snatching him up in both arms, squeezing him close
till it hurt, and praying for him to the Mother of God, holding him out
in both arms to the image as though to put him under the Mother’s
protection ... and suddenly a nurse runs in and snatches him from her
in terror. That was the picture! And Alyosha remembered his mother’s
face at that minute. He used to say that it was frenzied but beautiful
as he remembered. But he rarely cared to speak of this memory to any
one. In his childhood and youth he was by no means expansive, and
talked little indeed, but not from shyness or a sullen unsociability;
quite the contrary, from something different, from a sort of inner
preoccupation entirely personal and unconcerned with other people, but
so important to him that he seemed, as it were, to forget others on
account of it. But he was fond of people: he seemed throughout his life
to put implicit trust in people: yet no one ever looked on him as a
simpleton or naïve person. There was something about him which made one
feel at once (and it was so all his life afterwards) that he did not
care to be a judge of others—that he would never take it upon himself
to criticize and would never condemn any one for anything. He seemed,
indeed, to accept everything without the least condemnation though
often grieving bitterly: and this was so much so that no one could
surprise or frighten him even in his earliest youth. Coming at twenty
to his father’s house, which was a very sink of filthy debauchery, he,
chaste and pure as he was, simply withdrew in silence when to look on
was unbearable, but without the slightest sign of contempt or
condemnation. His father, who had once been in a dependent position,
and so was sensitive and ready to take offense, met him at first with
distrust and sullenness. “He does not say much,” he used to say, “and
thinks the more.” But soon, within a fortnight indeed, he took to
embracing him and kissing him terribly often, with drunken tears, with
sottish sentimentality, yet he evidently felt a real and deep affection
for him, such as he had never been capable of feeling for any one
before.
DEBUG:root:Documents: ['Document 1 content', 'Document 2 content']
DEBUG:root:Context for persona generation: Document 1 content Document 2 content
DEBUG:openai._base_client:Request options: {'method': 'post', 'url': '/chat/completions', 'files': None, 'json_data': {'messages': [{'role': 'system', 'content': 'You are a helpful assistant that creates personas based on input text and context.'}, {'role': 'user', 'content': 'Generate a persona for the following text: \nThe family, I repeat, was now united for the first time, and some of\nits members met for the first time in their lives. The younger brother,\nAlexey, had been a year already among us, having been the first of the\nthree to arrive. It is of that brother Alexey I find it most difficult\nto speak in this introduction. Yet I must give some preliminary account\nof him, if only to explain one queer fact, which is that I have to\nintroduce my hero to the reader wearing the cassock of a novice. Yes,\nhe had been for the last year in our monastery, and seemed willing to\nbe cloistered there for the rest of his life.\n\n\n\n\nChapter IV.\nThe Third Son, Alyosha\n\n\nHe was only twenty, his brother Ivan was in his twenty‐fourth year at\nthe time, while their elder brother Dmitri was twenty‐seven. First of\nall, I must explain that this young man, Alyosha, was not a fanatic,\nand, in my opinion at least, was not even a mystic. I may as well give\nmy full opinion from the beginning. He was simply an early lover of\nhumanity, and that he adopted the monastic life was simply because at\nthat time it struck him, so to say, as the ideal escape for his soul\nstruggling from the darkness of worldly wickedness to the light of\nlove. And the reason this life struck him in this way was that he found\nin it at that time, as he thought, an extraordinary being, our\ncelebrated elder, Zossima, to whom he became attached with all the warm\nfirst love of his ardent heart. But I do not dispute that he was very\nstrange even at that time, and had been so indeed from his cradle. I\nhave mentioned already, by the way, that though he lost his mother in\nhis fourth year he remembered her all his life—her face, her caresses,\n“as though she stood living before me.” Such memories may persist, as\nevery one knows, from an even earlier age, even from two years old, but\nscarcely standing out through a whole lifetime like spots of light out\nof darkness, like a corner torn out of a huge picture, which has all\nfaded and disappeared except that fragment. That is how it was with\nhim. He remembered one still summer evening, an open window, the\nslanting rays of the setting sun (that he recalled most vividly of\nall); in a corner of the room the holy image, before it a lighted lamp,\nand on her knees before the image his mother, sobbing hysterically with\ncries and moans, snatching him up in both arms, squeezing him close\ntill it hurt, and praying for him to the Mother of God, holding him out\nin both arms to the image as though to put him under the Mother’s\nprotection ... and suddenly a nurse runs in and snatches him from her\nin terror. That was the picture! And Alyosha remembered his mother’s\nface at that minute. He used to say that it was frenzied but beautiful\nas he remembered. But he rarely cared to speak of this memory to any\none. In his childhood and youth he was by no means expansive, and\ntalked little indeed, but not from shyness or a sullen unsociability;\nquite the contrary, from something different, from a sort of inner\npreoccupation entirely personal and unconcerned with other people, but\nso important to him that he seemed, as it were, to forget others on\naccount of it. But he was fond of people: he seemed throughout his life\nto put implicit trust in people: yet no one ever looked on him as a\nsimpleton or naïve person. There was something about him which made one\nfeel at once (and it was so all his life afterwards) that he did not\ncare to be a judge of others—that he would never take it upon himself\nto criticize and would never condemn any one for anything. He seemed,\nindeed, to accept everything without the least condemnation though\noften grieving bitterly: and this was so much so that no one could\nsurprise or frighten him even in his earliest youth. Coming at twenty\nto his father’s house, which was a very sink of filthy debauchery, he,\nchaste and pure as he was, simply withdrew in silence when to look on\nwas unbearable, but without the slightest sign of contempt or\ncondemnation. His father, who had once been in a dependent position,\nand so was sensitive and ready to take offense, met him at first with\ndistrust and sullenness. “He does not say much,” he used to say, “and\nthinks the more.” But soon, within a fortnight indeed, he took to\nembracing him and kissing him terribly often, with drunken tears, with\nsottish sentimentality, yet he evidently felt a real and deep affection\nfor him, such as he had never been capable of feeling for any one\nbefore.\n\nContext: Document 1 content Document 2 content'}], 'model': 'gpt-4o-mini', 'max_tokens': 1000, 'temperature': 0.7}}
DEBUG:openai._base_client:Sending HTTP Request: POST https://api.openai.com/v1/chat/completions
DEBUG:httpcore.connection:connect_tcp.started host='api.openai.com' port=443 local_address=None timeout=5.0 socket_options=None
DEBUG:httpcore.connection:connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x121d1a350>
DEBUG:httpcore.connection:start_tls.started ssl_context=<ssl.SSLContext object at 0x121c9a2a0> server_hostname='api.openai.com' timeout=5.0
DEBUG:httpcore.connection:start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x120154550>
DEBUG:httpcore.http11:send_request_headers.started request=<Request [b'POST']>
DEBUG:httpcore.http11:send_request_headers.complete
DEBUG:httpcore.http11:send_request_body.started request=<Request [b'POST']>
DEBUG:httpcore.http11:send_request_body.complete
DEBUG:httpcore.http11:receive_response_headers.started request=<Request [b'POST']>
DEBUG:httpcore.http11:receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'OK', [(b'Date', b'Mon, 02 Dec 2024 17:33:43 GMT'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'access-control-expose-headers', b'X-Request-ID'), (b'openai-organization', b'user-x1yzfup2szxob8d8i82godng'), (b'openai-processing-ms', b'11152'), (b'openai-version', b'2020-10-01'), (b'x-ratelimit-limit-requests', b'10000'), (b'x-ratelimit-limit-tokens', b'200000'), (b'x-ratelimit-remaining-requests', b'9999'), (b'x-ratelimit-remaining-tokens', b'197832'), (b'x-ratelimit-reset-requests', b'8.64s'), (b'x-ratelimit-reset-tokens', b'650ms'), (b'x-request-id', b'req_0b47342b542a40d5db8bcacdcbf89a4a'), (b'strict-transport-security', b'max-age=31536000; includeSubDomains; preload'), (b'CF-Cache-Status', b'DYNAMIC'), (b'Set-Cookie', b'__cf_bm=oAVAjflpf7y2Q4WjsluqC3y7ICT9trf63qy3V6.eYbU-1733160823-1.0.1.1-5kCU2w1j_CETSofLU17OhrouGyBxK77y8.KjSBEmHj.X_BSDBh4LYdID9TrCpZNm66vemmWPOiBTrQiio3cVCA; path=/; expires=Mon, 02-Dec-24 18:03:43 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None'), (b'X-Content-Type-Options', b'nosniff'), (b'Set-Cookie', b'_cfuvid=0yi7QqeFvSpiVKuJHqo6O2qUPzCvTueWQJQh8xhNvoo-1733160823811-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None'), (b'Server', b'cloudflare'), (b'CF-RAY', b'8ebd10064fa2c302-IAH'), (b'Content-Encoding', b'gzip'), (b'alt-svc', b'h3=":443"; ma=86400')])
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
DEBUG:httpcore.http11:receive_response_body.started request=<Request [b'POST']>
DEBUG:httpcore.http11:receive_response_body.complete
DEBUG:httpcore.http11:response_closed.started
DEBUG:httpcore.http11:response_closed.complete
DEBUG:openai._base_client:HTTP Response: POST https://api.openai.com/v1/chat/completions "200 OK" Headers([('date', 'Mon, 02 Dec 2024 17:33:43 GMT'), ('content-type', 'application/json'), ('transfer-encoding', 'chunked'), ('connection', 'keep-alive'), ('access-control-expose-headers', 'X-Request-ID'), ('openai-organization', 'user-x1yzfup2szxob8d8i82godng'), ('openai-processing-ms', '11152'), ('openai-version', '2020-10-01'), ('x-ratelimit-limit-requests', '10000'), ('x-ratelimit-limit-tokens', '200000'), ('x-ratelimit-remaining-requests', '9999'), ('x-ratelimit-remaining-tokens', '197832'), ('x-ratelimit-reset-requests', '8.64s'), ('x-ratelimit-reset-tokens', '650ms'), ('x-request-id', 'req_0b47342b542a40d5db8bcacdcbf89a4a'), ('strict-transport-security', 'max-age=31536000; includeSubDomains; preload'), ('cf-cache-status', 'DYNAMIC'), ('set-cookie', '__cf_bm=oAVAjflpf7y2Q4WjsluqC3y7ICT9trf63qy3V6.eYbU-1733160823-1.0.1.1-5kCU2w1j_CETSofLU17OhrouGyBxK77y8.KjSBEmHj.X_BSDBh4LYdID9TrCpZNm66vemmWPOiBTrQiio3cVCA; path=/; expires=Mon, 02-Dec-24 18:03:43 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None'), ('x-content-type-options', 'nosniff'), ('set-cookie', '_cfuvid=0yi7QqeFvSpiVKuJHqo6O2qUPzCvTueWQJQh8xhNvoo-1733160823811-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None'), ('server', 'cloudflare'), ('cf-ray', '8ebd10064fa2c302-IAH'), ('content-encoding', 'gzip'), ('alt-svc', 'h3=":443"; ma=86400')])
DEBUG:openai._base_client:request_id: req_0b47342b542a40d5db8bcacdcbf89a4a
ERROR:root:Error during persona generation: 'ChatCompletion' object is not subscriptable
Internal Server Error: /api/generate-persona/
ERROR:django.request:Internal Server Error: /api/generate-persona/
[02/Dec/2024 17:33:44] "POST /api/generate-persona/ HTTP/1.1" 500 56
import faiss
import numpy as np
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import openai
import os
import networkx as nx
import logging
from dotenv import load_dotenv
# Configure logging
logging.basicConfig(level=logging.DEBUG)
# Load OpenAI API key from environment variable
load_dotenv()
openai.api_key = os.getenv('OPENAI_API_KEY')
class PersonaGenerationView(APIView):
def post(self, request):
logging.debug(f"Request data: {request.data}")
input_text = request.data.get('text', None)
document_texts = request.data.get('documents', [])
# Validate input_text
if not input_text:
logging.debug("No input text provided.")
return Response(
{'error': 'The "text" field is required.'},
status=status.HTTP_400_BAD_REQUEST
)
# Validate document_texts
if not isinstance(document_texts, list):
logging.debug("Documents field is not a list.")
return Response(
{'error': 'The "documents" field must be a list of strings.'},
status=status.HTTP_400_BAD_REQUEST
)
logging.debug(f"Input text: {input_text}")
logging.debug(f"Documents: {document_texts}")
try:
# Combine relevant documents into context
context = " ".join(document_texts)
logging.debug(f"Context for persona generation: {context}")
# Generate persona using ChatCompletion
persona_response = openai.chat.completions.create(
model="gpt-4o-mini", # Ensure this model name is correct and accessible
messages=[
{
"role": "system",
"content": (
"You are a helpful assistant that creates personas based on input text and context."
)
},
{
"role": "user",
"content": (
f"Generate a persona for the following text: {input_text}\n\nContext: {context}"
)
}
],
max_tokens=1000, # Adjust as needed
temperature=0.7 # Adjust for creativity
)
persona = persona_response['choices'][0]['message']['content']
logging.debug(f"Generated persona: {persona}")
# Create graph
G = nx.DiGraph()
G.add_node(persona)
G.add_node(input_text)
# Generate prompt (edge) using ChatCompletion
prompt_response = openai.chat.completions.create(
model="gpt-4o-mini", # Ensure this model name is correct and accessible
messages=[
{
"role": "system",
"content": "Create a prompt based on the persona."
},
{
"role": "user",
"content": f"Generate a prompt for this persona: {persona}"
}
],
max_tokens=500, # Adjust as needed
temperature=0.7 # Adjust for creativity
)
prompt = prompt_response['choices'][0]['message']['content']
logging.debug(f"Generated prompt: {prompt}")
G.add_edge(input_text, persona, prompt=prompt)
data = nx.readwrite.json_graph.node_link_data(G)
for link in data['links']:
link['label'] = link.pop('prompt') # Rename 'prompt' to 'label' if required by frontend
logging.debug(f"Graph data: {data}")
return Response(data, status=status.HTTP_200_OK)
except Exception as e:
logging.error(f"Error during persona generation: {e}")
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
import faiss
import numpy as np
from openai.embeddings_utils import get_embedding
from openai import OpenAI
import pandas as pd
client = OpenAI()
def get_embedding(text, model="text-embedding-3-small"):
text = text.replace("\n", " ")
return client.embeddings.create(input = [text], model=model).data[0].embedding
df['ada_embedding'] = df.combined.apply(lambda x: get_embedding(x, model='text-embedding-3-small'))
df.to_csv('output/embedded_1k_reviews.csv', index=False)
df = pd.read_csv('output/embedded_1k_reviews.csv')
df['ada_embedding'] = df.ada_embedding.apply(eval).apply(np.array)
def embed_documents(documents):
"""
Generate embeddings for a list of documents using OpenAI's embeddings API.
"""
if not documents:
return []
try:
embeddings = [get_embedding(doc, engine='text-embedding-ada-002') for doc in documents]
return embeddings
except Exception as e:
logging.error(f"Error generating embeddings: {e}")
raise
def create_faiss_index(embeddings):
"""
Create a FAISS index for the embeddings.
"""
if not embeddings:
raise ValueError("Embeddings list is empty; cannot create FAISS index.")
try:
dimension = len(embeddings[0])
index = faiss.IndexFlatL2(dimension)
index.add(np.array(embeddings).astype('float32'))
return index
except Exception as e:
logging.error(f"Error creating FAISS index: {e}")
raise
def retrieve_relevant_docs(query, documents, index, embeddings, k=5):
"""
Retrieve relevant documents for a given query.
"""
try:
query_embedding = get_embedding(query, engine='text-embedding-ada-002')
D, I = index.search(np.array([query_embedding]).astype('float32'), k)
return [documents[i] for i in I[0] if i < len(documents)]
except Exception as e:
logging.error(f"Error retrieving relevant documents: {e}")
raise
from django.urls import path
from .views import PersonaGenerationView
urlpatterns = [
path('generate-persona/', PersonaGenerationView.as_view(), name='generate-persona'),
]
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
]
import React, { useState } from 'react';
import axios from 'axios';
import { Graph } from 'react-d3-graph'; // Ensure react-d3-graph is installed
function App() {
const [inputText, setInputText] = useState('');
const [graphData, setGraphData] = useState(null);
const [loading, setLoading] = useState(false); // Optional: for loading state
const [error, setError] = useState(null); // Optional: for error handling
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const response = await axios.post('http://127.0.0.1:8000/api/generate-persona/', {
text: inputText,
documents: ["Document 1 content", "Document 2 content"], // Adjust as necessary
});
console.log('API Response:', response.data);
const data = response.data;
setGraphData(data);
} catch (error) {
console.error('Error:', error.response || error.message);
setError(error.response?.data?.error || 'An unexpected error occurred.');
} finally {
setLoading(false);
}
};
const myConfig = {
nodeHighlightBehavior: true,
node: {
color: 'lightgreen',
size: 120,
highlightStrokeColor: 'blue',
},
link: {
highlightColor: 'lightblue',
renderLabel: true,
},
};
return (
<div style={{ padding: '20px' }}>
<h1>Persona Graph Generator</h1>
<form onSubmit={handleSubmit}>
<textarea
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Enter text here"
rows="5"
cols="50"
style={{ width: '100%', padding: '10px' }}
required
/>
<br />
<button type="submit" disabled={loading} style={{ padding: '10px 20px', marginTop: '10px' }}>
{loading ? 'Generating...' : 'Generate Persona Graph'}
</button>
</form>
{error && (
<div style={{ color: 'red', marginTop: '20px' }}>
<strong>Error:</strong> {error}
</div>
)}
{graphData && (
<div style={{ marginTop: '40px' }}>
<h2>Generated Graph</h2>
<Graph
id="graph-id" // id is mandatory
data={graphData}
config={myConfig}
/>
</div>
)}
</div>
);
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
The error message you're encountering:
```
ERROR:root:Error during persona generation: 'ChatCompletion' object is not subscriptable
```
indicates that your code is attempting to access the `ChatCompletion` response object using dictionary-style subscripting (e.g., `response['choices'][0]['message']['content']`). However, in the OpenAI Python library, the response from the `ChatCompletion.create` method is an object, not a dictionary. Therefore, you should use attribute access instead of subscripting.
Additionally, there are a couple of other points to address in your code to ensure it functions correctly:
1. **Incorrect Model Names:**
- **`gpt-4o-mini`**: This does not appear to be a valid model name. You likely intended to use `"gpt-4"`, `"gpt-3.5-turbo"`, or another valid model.
- **`text-embedding-3-small`**: This also seems incorrect. The typical model names for embeddings are like `"text-embedding-ada-002"`.
2. **Accessing OpenAI API Responses:**
- Use attribute access (`response.choices[0].message.content`) instead of dictionary-style access (`response['choices'][0]['message']['content']`).
3. **Ensure OpenAI Library is Updated:**
- Make sure you're using the latest version of the OpenAI Python library to benefit from the latest features and fixes.
Here's how you can modify your code to address these issues:
### 1. Update the Model Names
Ensure you're using valid model names. For example:
- Replace `"gpt-4o-mini"` with `"gpt-4"` or another valid model.
- Replace `"text-embedding-3-small"` with `"text-embedding-ada-002"`.
### 2. Modify the Response Access
Change how you access the response from the OpenAI API. Here's the updated `PersonaGenerationView`:
```python
import faiss
import numpy as np
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import openai
import os
import networkx as nx
import logging
from dotenv import load_dotenv
# Configure logging
logging.basicConfig(level=logging.DEBUG)
# Load OpenAI API key from environment variable
load_dotenv()
openai.api_key = os.getenv('OPENAI_API_KEY')
class PersonaGenerationView(APIView):
def post(self, request):
logging.debug(f"Request data: {request.data}")
input_text = request.data.get('text', None)
document_texts = request.data.get('documents', [])
# Validate input_text
if not input_text:
logging.debug("No input text provided.")
return Response(
{'error': 'The "text" field is required.'},
status=status.HTTP_400_BAD_REQUEST
)
# Validate document_texts
if not isinstance(document_texts, list):
logging.debug("Documents field is not a list.")
return Response(
{'error': 'The "documents" field must be a list of strings.'},
status=status.HTTP_400_BAD_REQUEST
)
logging.debug(f"Input text: {input_text}")
logging.debug(f"Documents: {document_texts}")
try:
# Combine relevant documents into context
context = " ".join(document_texts)
logging.debug(f"Context for persona generation: {context}")
# Generate persona using ChatCompletion
persona_response = openai.ChatCompletion.create(
model="gpt-4", # Updated model name
messages=[
{
"role": "system",
"content": (
"You are a helpful assistant that creates personas based on input text and context."
)
},
{
"role": "user",
"content": (
f"Generate a persona for the following text: {input_text}\n\nContext: {context}"
)
}
],
max_tokens=1000, # Adjust as needed
temperature=0.7 # Adjust for creativity
)
# Use attribute access instead of subscripting
persona = persona_response.choices[0].message.content
logging.debug(f"Generated persona: {persona}")
# Create graph
G = nx.DiGraph()
G.add_node(persona)
G.add_node(input_text)
# Generate prompt (edge) using ChatCompletion
prompt_response = openai.ChatCompletion.create(
model="gpt-4", # Updated model name
messages=[
{
"role": "system",
"content": "Create a prompt based on the persona."
},
{
"role": "user",
"content": f"Generate a prompt for this persona: {persona}"
}
],
max_tokens=500, # Adjust as needed
temperature=0.7 # Adjust for creativity
)
# Use attribute access instead of subscripting
prompt = prompt_response.choices[0].message.content
logging.debug(f"Generated prompt: {prompt}")
G.add_edge(input_text, persona, prompt=prompt)
data = nx.readwrite.json_graph.node_link_data(G)
for link in data['links']:
link['label'] = link.pop('prompt') # Rename 'prompt' to 'label' if required by frontend
logging.debug(f"Graph data: {data}")
return Response(data, status=status.HTTP_200_OK)
except Exception as e:
logging.error(f"Error during persona generation: {e}")
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
```
### 3. Update Embedding Functions
Ensure that the embedding functions use the correct model name and access patterns:
```python
import faiss
import numpy as np
from openai.embeddings_utils import get_embedding
import pandas as pd
import logging
# Initialize OpenAI client
# Note: The OpenAI Python library doesn't require explicit client initialization
# Ensure openai.api_key is set as done earlier
def get_embedding(text, model="text-embedding-ada-002"):
text = text.replace("\n", " ")
try:
response = openai.Embedding.create(input=[text], model=model)
return response['data'][0]['embedding']
except Exception as e:
logging.error(f"Error generating embedding for text: {e}")
raise
# Example usage with pandas
df = pd.read_csv('input.csv') # Replace with your actual data source
df['ada_embedding'] = df['combined'].apply(lambda x: get_embedding(x, model='text-embedding-ada-002'))
df.to_csv('output/embedded_1k_reviews.csv', index=False)
# Loading embeddings
df = pd.read_csv('output/embedded_1k_reviews.csv')
df['ada_embedding'] = df['ada_embedding'].apply(eval).apply(np.array)
def embed_documents(documents):
"""
Generate embeddings for a list of documents using OpenAI's embeddings API.
"""
if not documents:
return []
try:
embeddings = [get_embedding(doc, model='text-embedding-ada-002') for doc in documents]
return embeddings
except Exception as e:
logging.error(f"Error generating embeddings: {e}")
raise
def create_faiss_index(embeddings):
"""
Create a FAISS index for the embeddings.
"""
if not embeddings:
raise ValueError("Embeddings list is empty; cannot create FAISS index.")
try:
dimension = len(embeddings[0])
index = faiss.IndexFlatL2(dimension)
index.add(np.array(embeddings).astype('float32'))
return index
except Exception as e:
logging.error(f"Error creating FAISS index: {e}")
raise
def retrieve_relevant_docs(query, documents, index, embeddings, k=5):
"""
Retrieve relevant documents for a given query.
"""
try:
query_embedding = get_embedding(query, model='text-embedding-ada-002')
D, I = index.search(np.array([query_embedding]).astype('float32'), k)
return [documents[i] for i in I[0] if i < len(documents)]
except Exception as e:
logging.error(f"Error retrieving relevant documents: {e}")
raise
```
### 4. Ensure URL Configuration is Correct
Make sure your URL configurations are properly set up. Here's how your `urls.py` should look:
```python
from django.urls import path
from .views import PersonaGenerationView
urlpatterns = [
path('generate-persona/', PersonaGenerationView.as_view(), name='generate-persona'),
]
```
And your project's main `urls.py`:
```python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
]
```
### 5. Frontend Considerations
Your React frontend seems mostly fine, but ensure that the `react-d3-graph` library is properly installed and configured. Additionally, consider handling CORS if your frontend and backend are on different domains or ports.
### 6. Additional Recommendations
- **Environment Variables:** Ensure that your `.env` file contains the correct `OPENAI_API_KEY` and that it's being loaded properly.
- **Error Handling:** You might want to add more granular error handling to provide better feedback depending on where the failure occurs.
- **Testing:** After making these changes, test the API endpoint independently (e.g., using Postman or cURL) to ensure it's working before integrating with the frontend.
### 7. Example cURL Request for Testing
Before testing with the frontend, you can use `cURL` to ensure the API endpoint works as expected:
```bash
curl -X POST http://127.0.0.1:8000/api/generate-persona/ \
-H "Content-Type: application/json" \
-d '{
"text": "Your input text here",
"documents": ["Document 1 content", "Document 2 content"]
}'
```
This will help you isolate whether the issue is on the backend or frontend.
### 8. Update Dependencies
Ensure all your Python dependencies are up-to-date, especially the OpenAI library:
```bash
pip install --upgrade openai
pip install --upgrade faiss-cpu # or faiss-gpu if using GPU
pip install --upgrade networkx
pip install --upgrade django
pip install --upgrade djangorestframework
```
### Summary
The primary issue causing the `'ChatCompletion' object is not subscriptable` error is the incorrect method of accessing the response from the OpenAI API. By switching to attribute access and ensuring you're using valid model names, you should resolve the error. Additionally, make sure all other parts of your code and environment are correctly configured to support these changes.
If after making these adjustments you still encounter issues, consider enabling more detailed logging or using debugging tools to trace the exact point of failure.
o1-mini
ERROR
Cannot read properties of undefined (reading 'undefined')
TypeError: Cannot read properties of undefined (reading 'undefined')
at SVGGElement.<anonymous> (http://localhost:3000/static/js/bundle.js:6112:24)
at Dispatch.call (http://localhost:3000/static/js/bundle.js:49152:72)
at Object.gesture [as mouse] (http://localhost:3000/static/js/bundle.js:49357:16)
at mousemoved (http://localhost:3000/static/js/bundle.js:49274:14)
at http://localhost:3000/static/js/bundle.js:52956:14
The error you're encountering on the frontend:
```
ERROR
Cannot read properties of undefined (reading 'undefined')
TypeError: Cannot read properties of undefined (reading 'undefined')
at SVGGElement.<anonymous> (http://localhost:3000/static/js/bundle.js:6112:24)
at Dispatch.call (http://localhost:3000/static/js/bundle.js:49152:72)
at Object.gesture [as mouse] (http://localhost:3000/static/js/bundle.js:49357:16)
at mousemoved (http://localhost:3000/static/js/bundle.js:49274:14)
at http://localhost:3000/static/js/bundle.js:52956:14
```
indicates that your React frontend is attempting to access a property of an `undefined` object. Given that this error arises within the `react-d3-graph` library, it's likely related to the structure or content of the `graphData` being passed to the `Graph` component.
Let's systematically address both the backend and frontend to resolve this issue.
## 1. **Understanding the Root Cause**
### **Backend Data Structure**
Your Django backend is responsible for generating and sending the graph data to the frontend. The `react-d3-graph` library expects the data in a specific format:
```json
{
"nodes": [
{ "id": "node1" },
{ "id": "node2" },
...
],
"links": [
{ "source": "node1", "target": "node2", "label": "Edge Label" },
...
]
}
```
However, if the node `id`s are long strings or contain special characters, it might cause unexpected behavior in the frontend. Additionally, the frontend expects each node to have a unique and simple `id`.
### **Frontend Expectations**
The React component expects `graphData` to have `nodes` and `links` arrays with correctly formatted objects. If any node or link lacks required properties, or if the structure is not as expected, it can lead to the `TypeError` you're seeing.
## 2. **Steps to Resolve the Issue**
### **A. Modify the Backend to Ensure Proper Data Structure**
1. **Use Unique and Simple Node IDs:**
Instead of using the entire input text or persona text as node `id`s, which can be long and contain special characters, assign unique short identifiers to each node. This ensures compatibility with `react-d3-graph` and avoids potential issues with rendering.
2. **Include Labels for Display:**
Use separate fields for displaying node labels while keeping `id`s simple. This separation helps maintain clarity and prevents rendering issues.
3. **Update the Django View:**
Here's how you can modify your `PersonaGenerationView` to generate a properly structured graph:
```python
import faiss
import numpy as np
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import openai
import os
import networkx as nx
import logging
from dotenv import load_dotenv
import uuid # Import uuid for unique identifiers
# Configure logging
logging.basicConfig(level=logging.DEBUG)
# Load OpenAI API key from environment variable
load_dotenv()
openai.api_key = os.getenv('OPENAI_API_KEY')
class PersonaGenerationView(APIView):
def post(self, request):
logging.debug(f"Request data: {request.data}")
input_text = request.data.get('text', None)
document_texts = request.data.get('documents', [])
# Validate input_text
if not input_text:
logging.debug("No input text provided.")
return Response(
{'error': 'The "text" field is required.'},
status=status.HTTP_400_BAD_REQUEST
)
# Validate document_texts
if not isinstance(document_texts, list):
logging.debug("Documents field is not a list.")
return Response(
{'error': 'The "documents" field must be a list of strings.'},
status=status.HTTP_400_BAD_REQUEST
)
logging.debug(f"Input text: {input_text}")
logging.debug(f"Documents: {document_texts}")
try:
# Combine relevant documents into context
context = " ".join(document_texts)
logging.debug(f"Context for persona generation: {context}")
# Generate persona using ChatCompletion
persona_response = openai.ChatCompletion.create(
model="gpt-4", # Updated to a valid model name
messages=[
{
"role": "system",
"content": (
"You are a helpful assistant that creates personas based on input text and context."
)
},
{
"role": "user",
"content": (
f"Generate a persona for the following text: {input_text}\n\nContext: {context}"
)
}
],
max_tokens=1000,
temperature=0.7
)
# Use attribute access
persona = persona_response.choices[0].message.content
logging.debug(f"Generated persona: {persona}")
# Generate unique IDs for nodes
input_id = str(uuid.uuid4())
persona_id = str(uuid.uuid4())
# Create graph with unique node IDs and labels
G = nx.DiGraph()
G.add_node(input_id, label="Input Text")
G.add_node(persona_id, label="Persona")
# Generate prompt (edge) using ChatCompletion
prompt_response = openai.ChatCompletion.create(
model="gpt-4",
messages=[
{
"role": "system",
"content": "Create a prompt based on the persona."
},
{
"role": "user",
"content": f"Generate a prompt for this persona: {persona}"
}
],
max_tokens=500,
temperature=0.7
)
# Use attribute access
prompt = prompt_response.choices[0].message.content
logging.debug(f"Generated prompt: {prompt}")
# Add edge with label
G.add_edge(input_id, persona_id, label=prompt)
# Convert graph to node-link data
data = nx.readwrite.json_graph.node_link_data(G)
# Optionally, include labels in nodes
for node in data['nodes']:
node['name'] = node.get('label', node['id']) # Add a 'name' field for display
logging.debug(f"Graph data: {data}")
return Response(data, status=status.HTTP_200_OK)
except Exception as e:
logging.error(f"Error during persona generation: {e}")
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
```
**Explanation of Changes:**
- **Unique Identifiers:** Utilized Python's `uuid` library to generate unique and simple `id`s for each node (`input_id` and `persona_id`).
- **Labels for Display:** Added a `label` attribute to nodes for display purposes. Additionally, included a `name` field, which can be used by the frontend for rendering.
- **Edge Label:** Ensured that the edge includes a `label` field, which is required by your frontend configuration (`link.renderLabel: true`).
- **Attribute Access:** Corrected the response access method to use attribute access (`response.choices[0].message.content`).
### **B. Verify the Backend Response**
Before proceeding, ensure that your backend is returning the correct data structure. You can use tools like **Postman** or **cURL** to send a test request and inspect the response.
**Example cURL Request:**
```bash
curl -X POST http://127.0.0.1:8000/api/generate-persona/ \
-H "Content-Type: application/json" \
-d '{
"text": "Your input text here",
"documents": ["Document 1 content", "Document 2 content"]
}'
```
**Expected Response Structure:**
```json
{
"nodes": [
{ "id": "unique-input-id", "label": "Input Text", "name": "Input Text" },
{ "id": "unique-persona-id", "label": "Persona", "name": "Persona" }
],
"links": [
{ "source": "unique-input-id", "target": "unique-persona-id", "label": "Generated prompt here" }
]
}
```
**Key Points:**
- Each node has a unique `id` and a `name` for display.
- Each link has `source`, `target`, and `label` fields.
### **C. Adjust the Frontend to Handle the Updated Data Structure**
Given the backend now uses unique IDs and separate labels, adjust the frontend to utilize these fields correctly.
1. **Update the `react-d3-graph` Configuration:**
Ensure that the graph configuration aligns with the data structure. Specifically, if you're using `node.name` for display, configure the graph accordingly.
2. **Handle Node Labels Properly:**
If nodes have a separate `name` field for display, you might need to customize the node rendering to display the `name` instead of the `id`.
3. **Modify the React Component:**
Here's an updated version of your `App` component to handle the new data structure:
```javascript
import React, { useState } from 'react';
import axios from 'axios';
import { Graph } from 'react-d3-graph';
import './App.css'; // Ensure you have some basic styling
function App() {
const [inputText, setInputText] = useState('');
const [graphData, setGraphData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const response = await axios.post('http://127.0.0.1:8000/api/generate-persona/', {
text: inputText,
documents: ["Document 1 content", "Document 2 content"],
});
console.log('API Response:', response.data);
const data = response.data;
// Optionally, map node labels if needed
const formattedData = {
nodes: data.nodes.map(node => ({
id: node.id,
label: node.name || node.id, // Use 'name' for display if available
})),
links: data.links.map(link => ({
source: link.source,
target: link.target,
label: link.label,
})),
};
setGraphData(formattedData);
} catch (error) {
console.error('Error:', error.response || error.message);
setError(error.response?.data?.error || 'An unexpected error occurred.');
} finally {
setLoading(false);
}
};
const myConfig = {
nodeHighlightBehavior: true,
node: {
color: 'lightgreen',
size: 300, // Increased size for better visibility
highlightStrokeColor: 'blue',
labelProperty: 'label', // Use 'label' for node labels
},
link: {
highlightColor: 'lightblue',
renderLabel: true,
labelProperty: 'label', // Use 'label' for link labels
fontSize: 12, // Adjust font size as needed
},
directed: true, // To show arrowheads
height: 600, // Adjust height as needed
width: 800, // Adjust width as needed
};
return (
<div style={{ padding: '20px' }}>
<h1>Persona Graph Generator</h1>
<form onSubmit={handleSubmit}>
<textarea
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Enter text here"
rows="5"
cols="50"
style={{ width: '100%', padding: '10px' }}
required
/>
<br />
<button type="submit" disabled={loading} style={{ padding: '10px 20px', marginTop: '10px' }}>
{loading ? 'Generating...' : 'Generate Persona Graph'}
</button>
</form>
{error && (
<div style={{ color: 'red', marginTop: '20px' }}>
<strong>Error:</strong> {error}
</div>
)}
{graphData && (
<div style={{ marginTop: '40px' }}>
<h2>Generated Graph</h2>
<Graph
id="graph-id"
data={graphData}
config={myConfig}
/>
</div>
)}
</div>
);
}
export default App;
```
**Explanation of Changes:**
- **Formatted Data:** Mapped the backend response to ensure that nodes have an `id` and a `label`. This helps in rendering the graph correctly.
- **Graph Configuration:** Adjusted the `node` and `link` configurations to utilize the `label` property for displaying labels.
- **Graph Dimensions:** Increased the graph's `height` and `width` for better visibility, especially if node labels are long.
4. **Add Basic Styling (Optional):**
Ensure that your `App.css` (or equivalent CSS file) includes some basic styling to prevent layout issues.
```css
/* App.css */
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 0;
}
textarea {
font-size: 16px;
}
button {
font-size: 16px;
cursor: pointer;
}
```
### **D. Test the Integration**
1. **Restart the Backend Server:**
Ensure that your Django server is running and that the changes have been applied.
```bash
python manage.py runserver
```
2. **Run the Frontend:**
Start your React application.
```bash
npm start
```
3. **Submit a Request:**
- Enter some input text in the textarea.
- Click the "Generate Persona Graph" button.
- Monitor the network requests in your browser's developer tools to ensure that the API call is successful and that the response data matches the expected structure.
4. **Inspect the Console:**
- Check both the **backend logs** and the **browser console** for any errors or unexpected logs.
- Ensure that `console.log('API Response:', response.data);` outputs the correctly structured data.
### **E. Additional Debugging Steps**
If you still encounter issues, follow these steps to further isolate and resolve the problem.
1. **Inspect the API Response in Frontend:**
Before setting the graph data, log the formatted data to ensure it matches the expected structure.
```javascript
console.log('Formatted Graph Data:', formattedData);
```
2. **Handle Edge Cases:**
- **Empty Nodes or Links:** Ensure that neither `nodes` nor `links` arrays are empty.
- **Duplicate IDs:** Ensure that each node has a unique `id`.
- **Special Characters:** Avoid using special characters in node `id`s as they might interfere with rendering.
3. **Use Static Data for Testing:**
Temporarily use static graph data to verify that the frontend renders it correctly. This helps determine if the issue is with the data or the rendering logic.
```javascript
const staticData = {
nodes: [
{ id: 'node1', label: 'Input Text' },
{ id: 'node2', label: 'Persona' },
],
links: [
{ source: 'node1', target: 'node2', label: 'Sample Prompt' },
],
};
// Set this instead of API response to test
setGraphData(staticData);
```
If the static data renders correctly, the issue likely lies with the dynamic data from the backend.
4. **Enable Detailed Logging:**
Increase logging verbosity on both backend and frontend to capture more information about the data flow.
### **F. Ensure CORS is Properly Configured**
If your frontend is running on a different port (e.g., `localhost:3000`) than your backend (`localhost:8000`), ensure that Cross-Origin Resource Sharing (CORS) is properly configured in your Django backend.
1. **Install `django-cors-headers`:**
```bash
pip install django-cors-headers
```
2. **Add to Installed Apps and Middleware:**
```python
# settings.py
INSTALLED_APPS = [
...
'corsheaders',
...
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
]
# Allow all origins (for development purposes)
CORS_ALLOW_ALL_ORIGINS = True
# For production, specify allowed origins:
# CORS_ALLOWED_ORIGINS = [
# "http://localhost:3000",
# "https://your-frontend-domain.com",
# ]
```
3. **Restart the Backend Server:**
Apply the changes by restarting the server.
```bash
python manage.py runserver
```
### **G. Update Dependencies**
Ensure all your dependencies are up-to-date to avoid compatibility issues.
```bash
# Update OpenAI Python library
pip install --upgrade openai
# Update Django and related packages
pip install --upgrade django djangorestframework django-cors-headers
# Update frontend dependencies
cd your-react-app
npm install react-d3-graph axios
npm update
```
## 3. **Summary of Actions**
1. **Backend:**
- Use unique and simple `id`s for nodes.
- Include separate `label` fields for display purposes.
- Ensure the response structure matches what `react-d3-graph` expects.
- Correctly access OpenAI API responses using attribute access.
2. **Frontend:**
- Map backend data to match the expected format.
- Configure `react-d3-graph` to use the correct properties for labels.
- Handle and log API responses to verify data integrity.
- Optionally, use static data to verify frontend rendering.
3. **General:**
- Ensure CORS is correctly configured.
- Update all dependencies to their latest versions.
- Test each component (backend and frontend) independently before integrating.
By following these steps, you should be able to resolve the `TypeError` and ensure that the graph is rendered correctly in your React frontend. If you continue to encounter issues, please provide more details about the API response and any additional error logs for further assistance.
o1-mini
# main.py
import os
from dotenv import load_dotenv
from workflows.persona_workflow import create_persona_workflow
from workflows.response_workflow import create_response_workflow
from utils.input_utils import get_multiline_input
def main():
load_dotenv() # Load environment variables from .env file
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
print("Error: OPENAI_API_KEY not found in environment variables.")
return
persona_workflow = create_persona_workflow(api_key)
response_workflow = create_response_workflow(api_key)
print("\n=== Enhanced Persona Generator and Responder ===")
while True:
print("\nOptions:")
print("1. Generate Persona")
print("2. Generate Response")
print("3. Exit")
choice = input("\nEnter your choice: ").strip()
if choice == "3":
break
if choice == "1":
sample_text = get_multiline_input("\nEnter sample text:")
inputs = {"sample_text": sample_text}
try:
outputs = persona_workflow.run(inputs)
print(outputs.get("message", "Persona generation completed."))
except Exception as e:
print(f"Error: {str(e)}")
elif choice == "2":
prompt = get_multiline_input("\nEnter your prompt:")
inputs = {"prompt": prompt}
try:
outputs = response_workflow.run(inputs)
print(outputs.get("message", "Response generation completed."))
except Exception as e:
print(f"Error: {str(e)}")
else:
print("Invalid choice. Please select 1, 2, or 3.")
print("\nThank you for using the Enhanced Persona Generator and Responder!")
if __name__ == "__main__":
main()
import asyncio
import logging
from typing import Any
from autogen_core.application.logging import EVENT_LOGGER_NAME
from autogen_core.base import MessageContext
from autogen_core.components import RoutedAgent, message_handler
from autogen_magentic_one.messages import (
AgentEvent,
BroadcastMessage,
DeactivateMessage,
MagenticOneMessages,
RequestReplyMessage,
ResetMessage,
)
class MagenticOneBaseAgent(RoutedAgent):
"""An agent that optionally ensures messages are handled non-concurrently in the order they arrive."""
def __init__(
self,
description: str,
handle_messages_concurrently: bool = False,
) -> None:
super().__init__(description)
self._handle_messages_concurrently = handle_messages_concurrently
self._enabled = True
self.logger = logging.getLogger(EVENT_LOGGER_NAME + f".{self.id.key}.agent")
if not self._handle_messages_concurrently:
# TODO: make it possible to stop
self._message_queue = asyncio.Queue[tuple[MagenticOneMessages, MessageContext, asyncio.Future[Any]]]()
self._processing_task = asyncio.create_task(self._process())
async def _process(self) -> None:
while True:
message, ctx, future = await self._message_queue.get()
if ctx.cancellation_token.is_cancelled():
# TODO: Do we need to resolve the future here?
future.cancel()
continue
try:
if isinstance(message, RequestReplyMessage):
await self._handle_request_reply(message, ctx)
elif isinstance(message, BroadcastMessage):
await self._handle_broadcast(message, ctx)
elif isinstance(message, ResetMessage):
await self._handle_reset(message, ctx)
elif isinstance(message, DeactivateMessage):
await self._handle_deactivate(message, ctx)
else:
raise ValueError("Unknown message type.")
future.set_result(None)
except asyncio.CancelledError:
future.cancel()
except Exception as e:
future.set_exception(e)
@message_handler
async def handle_incoming_message(
self,
message: BroadcastMessage | ResetMessage | DeactivateMessage | RequestReplyMessage,
ctx: MessageContext,
) -> None:
if not self._enabled:
return
if self._handle_messages_concurrently:
if isinstance(message, RequestReplyMessage):
await self._handle_request_reply(message, ctx)
elif isinstance(message, BroadcastMessage):
await self._handle_broadcast(message, ctx)
elif isinstance(message, ResetMessage):
await self._handle_reset(message, ctx)
elif isinstance(message, DeactivateMessage):
await self._handle_deactivate(message, ctx)
else:
future = asyncio.Future[Any]()
await self._message_queue.put((message, ctx, future))
await future
async def _handle_broadcast(self, message: BroadcastMessage, ctx: MessageContext) -> None:
raise NotImplementedError()
async def _handle_reset(self, message: ResetMessage, ctx: MessageContext) -> None:
raise NotImplementedError()
async def _handle_request_reply(self, message: RequestReplyMessage, ctx: MessageContext) -> None:
raise NotImplementedError()
async def _handle_deactivate(self, message: DeactivateMessage, ctx: MessageContext) -> None:
"""Handle a deactivate message."""
self._enabled = False
self.logger.info(
AgentEvent(
f"{self.metadata['type']} (deactivated)",
"",
)
)
async def on_unhandled_message(self, message: Any, ctx: MessageContext) -> None:
"""Drop the message, with a log."""
# self.logger.info(
# AgentEvent(
# f"{self.metadata['type']} (unhandled message)",
# f"Unhandled message type: {type(message)}",
# )
# )
pass
import logging
import time
from typing import List, Optional
from autogen_core.application.logging import EVENT_LOGGER_NAME
from autogen_core.base import AgentProxy, CancellationToken, MessageContext
from autogen_core.components.models import AssistantMessage, LLMMessage, UserMessage
from ..messages import BroadcastMessage, OrchestrationEvent, RequestReplyMessage, ResetMessage
from ..utils import message_content_to_str
from .base_agent import MagenticOneBaseAgent
class BaseOrchestrator(MagenticOneBaseAgent):
"""Base class for orchestrator that manage a group of agents."""
def __init__(
self,
agents: List[AgentProxy],
description: str = "Base orchestrator",
max_rounds: int = 20,
max_time: float = float("inf"),
handle_messages_concurrently: bool = False,
) -> None:
super().__init__(description, handle_messages_concurrently=handle_messages_concurrently)
self._agents = agents
self._max_rounds = max_rounds
self._max_time = max_time
self._num_rounds = 0
self._start_time: float = -1.0
self.logger = logging.getLogger(EVENT_LOGGER_NAME + f".{self.id.key}.orchestrator")
async def _handle_broadcast(self, message: BroadcastMessage, ctx: MessageContext) -> None:
"""Handle an incoming message."""
# First broadcast sets the timer
if self._start_time < 0:
self._start_time = time.time()
source = "Unknown"
if isinstance(message.content, UserMessage) or isinstance(message.content, AssistantMessage):
source = message.content.source
content = message_content_to_str(message.content.content)
self.logger.info(OrchestrationEvent(source, content))
# Termination conditions
if self._num_rounds >= self._max_rounds:
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (termination condition)",
f"Max rounds ({self._max_rounds}) reached.",
)
)
return
if time.time() - self._start_time >= self._max_time:
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (termination condition)",
f"Max time ({self._max_time}s) reached.",
)
)
return
if message.request_halt:
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (termination condition)",
f"{source} requested halt.",
)
)
return
next_agent = await self._select_next_agent(message.content)
if next_agent is None:
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (termination condition)",
"No agent selected.",
)
)
return
request_reply_message = RequestReplyMessage()
# emit an event
self.logger.info(
OrchestrationEvent(
source=f"{self.metadata['type']} (thought)",
message=f"Next speaker {(await next_agent.metadata)['type']}" "",
)
)
self._num_rounds += 1 # Call before sending the message
await self.send_message(request_reply_message, next_agent.id, cancellation_token=ctx.cancellation_token)
async def _select_next_agent(self, message: LLMMessage) -> Optional[AgentProxy]:
raise NotImplementedError()
def get_max_rounds(self) -> int:
return self._max_rounds
async def _handle_reset(self, message: ResetMessage, ctx: MessageContext) -> None:
"""Handle a reset message."""
await self._reset(ctx.cancellation_token)
async def _reset(self, cancellation_token: CancellationToken) -> None:
pass
from typing import List, Tuple
from autogen_core.base import CancellationToken, MessageContext, TopicId
from autogen_core.components.models import (
AssistantMessage,
LLMMessage,
UserMessage,
)
from autogen_magentic_one.messages import (
BroadcastMessage,
RequestReplyMessage,
ResetMessage,
UserContent,
)
from ..utils import message_content_to_str
from .base_agent import MagenticOneBaseAgent
class BaseWorker(MagenticOneBaseAgent):
"""Base agent that handles the MagenticOne worker behavior protocol."""
def __init__(
self,
description: str,
handle_messages_concurrently: bool = False,
) -> None:
super().__init__(description, handle_messages_concurrently=handle_messages_concurrently)
self._chat_history: List[LLMMessage] = []
async def _handle_broadcast(self, message: BroadcastMessage, ctx: MessageContext) -> None:
assert isinstance(message.content, UserMessage)
self._chat_history.append(message.content)
async def _handle_reset(self, message: ResetMessage, ctx: MessageContext) -> None:
"""Handle a reset message."""
await self._reset(ctx.cancellation_token)
async def _handle_request_reply(self, message: RequestReplyMessage, ctx: MessageContext) -> None:
"""Respond to a reply request."""
request_halt, response = await self._generate_reply(ctx.cancellation_token)
assistant_message = AssistantMessage(content=message_content_to_str(response), source=self.metadata["type"])
self._chat_history.append(assistant_message)
user_message = UserMessage(content=response, source=self.metadata["type"])
topic_id = TopicId("default", self.id.key)
await self.publish_message(
BroadcastMessage(content=user_message, request_halt=request_halt),
topic_id=topic_id,
cancellation_token=ctx.cancellation_token,
)
async def _generate_reply(self, cancellation_token: CancellationToken) -> Tuple[bool, UserContent]:
"""Returns (request_halt, response_message)"""
raise NotImplementedError()
async def _reset(self, cancellation_token: CancellationToken) -> None:
self._chat_history = []
import re
from typing import Awaitable, Callable, List, Literal, Tuple, Union
from autogen_core.base import CancellationToken
from autogen_core.components import default_subscription
from autogen_core.components.code_executor import CodeBlock, CodeExecutor
from autogen_core.components.models import (
ChatCompletionClient,
SystemMessage,
UserMessage,
)
from ..messages import UserContent
from ..utils import message_content_to_str
from .base_worker import BaseWorker
@default_subscription
class Coder(BaseWorker):
"""An agent that can write code or text to solve tasks without additional tools."""
DEFAULT_DESCRIPTION = "A helpful and general-purpose AI assistant that has strong language skills, Python skills, and Linux command line skills."
DEFAULT_SYSTEM_MESSAGES = [
SystemMessage("""You are a helpful AI assistant.
Solve tasks using your coding and language skills.
In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute.
1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself.
2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly.
Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill.
When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user.
If you want the user to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user.
If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try.
When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible.
Reply "TERMINATE" in the end when everything is done.""")
]
def __init__(
self,
model_client: ChatCompletionClient,
description: str = DEFAULT_DESCRIPTION,
system_messages: List[SystemMessage] = DEFAULT_SYSTEM_MESSAGES,
request_terminate: bool = False,
) -> None:
super().__init__(description)
self._model_client = model_client
self._system_messages = system_messages
self._request_terminate = request_terminate
async def _generate_reply(self, cancellation_token: CancellationToken) -> Tuple[bool, UserContent]:
"""Respond to a reply request."""
# Make an inference to the model.
response = await self._model_client.create(
self._system_messages + self._chat_history, cancellation_token=cancellation_token
)
assert isinstance(response.content, str)
if self._request_terminate:
return "TERMINATE" in response.content, response.content
else:
return False, response.content
# True if the user confirms the code, False otherwise
ConfirmCode = Callable[[CodeBlock], Awaitable[bool]]
@default_subscription
class Executor(BaseWorker):
DEFAULT_DESCRIPTION = "A computer terminal that performs no other action than running Python scripts (provided to it quoted in ```python code blocks), or sh shell scripts (provided to it quoted in ```sh code blocks)"
def __init__(
self,
description: str = DEFAULT_DESCRIPTION,
check_last_n_message: int = 5,
*,
executor: CodeExecutor,
confirm_execution: ConfirmCode | Literal["ACCEPT_ALL"],
) -> None:
super().__init__(description)
self._executor = executor
self._check_last_n_message = check_last_n_message
self._confirm_execution = confirm_execution
async def _generate_reply(self, cancellation_token: CancellationToken) -> Tuple[bool, UserContent]:
"""Respond to a reply request."""
n_messages_checked = 0
for idx in range(len(self._chat_history)):
message = self._chat_history[-(idx + 1)]
if not isinstance(message, UserMessage):
continue
# Extract code block from the message.
code = self._extract_execution_request(message_content_to_str(message.content))
if code is not None:
code_lang = code[0]
code_block = code[1]
if code_lang == "py":
code_lang = "python"
execution_requests = [CodeBlock(code=code_block, language=code_lang)]
if self._confirm_execution == "ACCEPT_ALL" or await self._confirm_execution(execution_requests[0]): # type: ignore
result = await self._executor.execute_code_blocks(execution_requests, cancellation_token)
if result.output.strip() == "":
# Sometimes agents forget to print(). Remind the to print something
return (
False,
f"The script ran but produced no output to console. The Unix exit code was: {result.exit_code}. If you were expecting output, consider revising the script to ensure content is printed to stdout.",
)
else:
return (
False,
f"The script ran, then exited with Unix exit code: {result.exit_code}\nIts output was:\n{result.output}",
)
else:
return (
False,
"The code block was not confirmed by the user and so was not run.",
)
else:
n_messages_checked += 1
if n_messages_checked > self._check_last_n_message:
break
return (
False,
"No code block detected in the messages. Please provide a markdown-encoded code block to execute for the original task.",
)
def _extract_execution_request(self, markdown_text: str) -> Union[Tuple[str, str], None]:
pattern = r"```(\w+)\n(.*?)\n```"
# Search for the pattern in the markdown text
match = re.search(pattern, markdown_text, re.DOTALL)
# Extract the language and code block if a match is found
if match:
return (match.group(1), match.group(2))
return None
ORCHESTRATOR_SYSTEM_MESSAGE = ""
ORCHESTRATOR_CLOSED_BOOK_PROMPT = """Below I will present you a request. Before we begin addressing the request, please answer the following pre-survey to the best of your ability. Keep in mind that you are Ken Jennings-level with trivia, and Mensa-level with puzzles, so there should be a deep well to draw from.
Here is the request:
{task}
Here is the pre-survey:
1. Please list any specific facts or figures that are GIVEN in the request itself. It is possible that there are none.
2. Please list any facts that may need to be looked up, and WHERE SPECIFICALLY they might be found. In some cases, authoritative sources are mentioned in the request itself.
3. Please list any facts that may need to be derived (e.g., via logical deduction, simulation, or computation)
4. Please list any facts that are recalled from memory, hunches, well-reasoned guesses, etc.
When answering this survey, keep in mind that "facts" will typically be specific names, dates, statistics, etc. Your answer should use headings:
1. GIVEN OR VERIFIED FACTS
2. FACTS TO LOOK UP
3. FACTS TO DERIVE
4. EDUCATED GUESSES
DO NOT include any other headings or sections in your response. DO NOT list next steps or plans until asked to do so.
"""
ORCHESTRATOR_PLAN_PROMPT = """Fantastic. To address this request we have assembled the following team:
{team}
Based on the team composition, and known and unknown facts, please devise a short bullet-point plan for addressing the original request. Remember, there is no requirement to involve all team members -- a team member's particular expertise may not be needed for this task."""
ORCHESTRATOR_SYNTHESIZE_PROMPT = """
We are working to address the following user request:
{task}
To answer this request we have assembled the following team:
{team}
Here is an initial fact sheet to consider:
{facts}
Here is the plan to follow as best as possible:
{plan}
"""
ORCHESTRATOR_LEDGER_PROMPT = """
Recall we are working on the following request:
{task}
And we have assembled the following team:
{team}
To make progress on the request, please answer the following questions, including necessary reasoning:
- Is the request fully satisfied? (True if complete, or False if the original request has yet to be SUCCESSFULLY and FULLY addressed)
- Are we in a loop where we are repeating the same requests and / or getting the same responses as before? Loops can span multiple turns, and can include repeated actions like scrolling up or down more than a handful of times.
- Are we making forward progress? (True if just starting, or recent messages are adding value. False if recent messages show evidence of being stuck in a loop or if there is evidence of significant barriers to success such as the inability to read from a required file)
- Who should speak next? (select from: {names})
- What instruction or question would you give this team member? (Phrase as if speaking directly to them, and include any specific information they may need)
Please output an answer in pure JSON format according to the following schema. The JSON object must be parsable as-is. DO NOT OUTPUT ANYTHING OTHER THAN JSON, AND DO NOT DEVIATE FROM THIS SCHEMA:
{{
"is_request_satisfied": {{
"reason": string,
"answer": boolean
}},
"is_in_loop": {{
"reason": string,
"answer": boolean
}},
"is_progress_being_made": {{
"reason": string,
"answer": boolean
}},
"next_speaker": {{
"reason": string,
"answer": string (select from: {names})
}},
"instruction_or_question": {{
"reason": string,
"answer": string
}}
}}
"""
ORCHESTRATOR_UPDATE_FACTS_PROMPT = """As a reminder, we are working to solve the following task:
{task}
It's clear we aren't making as much progress as we would like, but we may have learned something new. Please rewrite the following fact sheet, updating it to include anything new we have learned that may be helpful. Example edits can include (but are not limited to) adding new guesses, moving educated guesses to verified facts if appropriate, etc. Updates may be made to any section of the fact sheet, and more than one section of the fact sheet can be edited. This is an especially good time to update educated guesses, so please at least add or update one educated guess or hunch, and explain your reasoning.
Here is the old fact sheet:
{facts}
"""
ORCHESTRATOR_UPDATE_PLAN_PROMPT = """Please briefly explain what went wrong on this last run (the root cause of the failure), and then come up with a new plan that takes steps and/or includes hints to overcome prior challenges and especially avoids repeating the same mistakes. As before, the new plan should be concise, be expressed in bullet-point form, and consider the following team composition (do not involve any other outside people since we cannot contact anyone else):
{team}
"""
ORCHESTRATOR_GET_FINAL_ANSWER = """
We are working on the following task:
{task}
We have completed the task.
The above messages contain the conversation that took place to complete the task.
Based on the information gathered, provide the final answer to the original request.
The answer should be phrased as if you were speaking to the user.
"""
import json
from typing import Any, Dict, List, Optional
from autogen_core.base import AgentProxy, CancellationToken, MessageContext, TopicId
from autogen_core.components import default_subscription
from autogen_core.components.models import (
AssistantMessage,
ChatCompletionClient,
LLMMessage,
SystemMessage,
UserMessage,
)
from ..messages import BroadcastMessage, OrchestrationEvent, ResetMessage
from .base_orchestrator import BaseOrchestrator
from .orchestrator_prompts import (
ORCHESTRATOR_CLOSED_BOOK_PROMPT,
ORCHESTRATOR_GET_FINAL_ANSWER,
ORCHESTRATOR_LEDGER_PROMPT,
ORCHESTRATOR_PLAN_PROMPT,
ORCHESTRATOR_SYNTHESIZE_PROMPT,
ORCHESTRATOR_SYSTEM_MESSAGE,
ORCHESTRATOR_UPDATE_FACTS_PROMPT,
ORCHESTRATOR_UPDATE_PLAN_PROMPT,
)
@default_subscription
class RoundRobinOrchestrator(BaseOrchestrator):
"""A simple orchestrator that selects agents in a round-robin fashion."""
def __init__(
self,
agents: List[AgentProxy],
description: str = "Round robin orchestrator",
max_rounds: int = 20,
) -> None:
super().__init__(agents=agents, description=description, max_rounds=max_rounds)
async def _select_next_agent(self, message: LLMMessage) -> AgentProxy:
self._current_index = (self._num_rounds) % len(self._agents)
return self._agents[self._current_index]
@default_subscription
class LedgerOrchestrator(BaseOrchestrator):
"""The LedgerOrhestrator is the orchestrator used by MagenticOne to solve tasks.
It uses a ledger (implemented as a JSON generated by the LLM) to keep track of task progress and select the next agent that should speak."""
DEFAULT_SYSTEM_MESSAGES = [
SystemMessage(ORCHESTRATOR_SYSTEM_MESSAGE),
]
def __init__(
self,
agents: List[AgentProxy],
model_client: ChatCompletionClient,
description: str = "Ledger-based orchestrator",
system_messages: List[SystemMessage] = DEFAULT_SYSTEM_MESSAGES,
closed_book_prompt: str = ORCHESTRATOR_CLOSED_BOOK_PROMPT,
plan_prompt: str = ORCHESTRATOR_PLAN_PROMPT,
synthesize_prompt: str = ORCHESTRATOR_SYNTHESIZE_PROMPT,
ledger_prompt: str = ORCHESTRATOR_LEDGER_PROMPT,
update_facts_prompt: str = ORCHESTRATOR_UPDATE_FACTS_PROMPT,
update_plan_prompt: str = ORCHESTRATOR_UPDATE_PLAN_PROMPT,
max_rounds: int = 20,
max_time: float = float("inf"),
max_stalls_before_replan: int = 3,
max_replans: int = 3,
return_final_answer: bool = False,
) -> None:
super().__init__(agents=agents, description=description, max_rounds=max_rounds, max_time=max_time)
self._model_client = model_client
# prompt-based parameters
self._system_messages = system_messages
self._closed_book_prompt = closed_book_prompt
self._plan_prompt = plan_prompt
self._synthesize_prompt = synthesize_prompt
self._ledger_prompt = ledger_prompt
self._update_facts_prompt = update_facts_prompt
self._update_plan_prompt = update_plan_prompt
self._chat_history: List[LLMMessage] = []
self._should_replan = True
self._max_stalls_before_replan = max_stalls_before_replan
self._stall_counter = 0
self._max_replans = max_replans
self._replan_counter = 0
self._return_final_answer = return_final_answer
self._team_description = ""
self._task = ""
self._facts = ""
self._plan = ""
def _get_closed_book_prompt(self, task: str) -> str:
return self._closed_book_prompt.format(task=task)
def _get_plan_prompt(self, team: str) -> str:
return self._plan_prompt.format(team=team)
def _get_synthesize_prompt(self, task: str, team: str, facts: str, plan: str) -> str:
return self._synthesize_prompt.format(task=task, team=team, facts=facts, plan=plan)
def _get_ledger_prompt(self, task: str, team: str, names: List[str]) -> str:
return self._ledger_prompt.format(task=task, team=team, names=names)
def _get_update_facts_prompt(self, task: str, facts: str) -> str:
return self._update_facts_prompt.format(task=task, facts=facts)
def _get_update_plan_prompt(self, team: str) -> str:
return self._update_plan_prompt.format(team=team)
async def _get_team_description(self) -> str:
# a single string description of all agents in the team
team_description = ""
for agent in self._agents:
metadata = await agent.metadata
name = metadata["type"]
description = metadata["description"]
team_description += f"{name}: {description}\n"
return team_description
async def _get_team_names(self) -> List[str]:
return [(await agent.metadata)["type"] for agent in self._agents]
def _get_message_str(self, message: LLMMessage) -> str:
if isinstance(message.content, str):
return message.content
else:
result = ""
for content in message.content:
if isinstance(content, str):
result += content + "\n"
assert len(result) > 0
return result
async def _initialize_task(self, task: str, cancellation_token: Optional[CancellationToken] = None) -> None:
# called the first time a task is received
self._task = task
self._team_description = await self._get_team_description()
# Shallow-copy the conversation
planning_conversation = [m for m in self._chat_history]
# 1. GATHER FACTS
# create a closed book task and generate a response and update the chat history
planning_conversation.append(
UserMessage(content=self._get_closed_book_prompt(self._task), source=self.metadata["type"])
)
response = await self._model_client.create(
self._system_messages + planning_conversation, cancellation_token=cancellation_token
)
assert isinstance(response.content, str)
self._facts = response.content
planning_conversation.append(AssistantMessage(content=self._facts, source=self.metadata["type"]))
# 2. CREATE A PLAN
## plan based on available information
planning_conversation.append(
UserMessage(content=self._get_plan_prompt(self._team_description), source=self.metadata["type"])
)
response = await self._model_client.create(
self._system_messages + planning_conversation, cancellation_token=cancellation_token
)
assert isinstance(response.content, str)
self._plan = response.content
# At this point, the planning conversation is dropped.
async def _update_facts_and_plan(self, cancellation_token: Optional[CancellationToken] = None) -> None:
# called when the orchestrator decides to replan
# Shallow-copy the conversation
planning_conversation = [m for m in self._chat_history]
# Update the facts
planning_conversation.append(
UserMessage(content=self._get_update_facts_prompt(self._task, self._facts), source=self.metadata["type"])
)
response = await self._model_client.create(
self._system_messages + planning_conversation, cancellation_token=cancellation_token
)
assert isinstance(response.content, str)
self._facts = response.content
planning_conversation.append(AssistantMessage(content=self._facts, source=self.metadata["type"]))
# Update the plan
planning_conversation.append(
UserMessage(content=self._get_update_plan_prompt(self._team_description), source=self.metadata["type"])
)
response = await self._model_client.create(
self._system_messages + planning_conversation, cancellation_token=cancellation_token
)
assert isinstance(response.content, str)
self._plan = response.content
async def update_ledger(self, cancellation_token: Optional[CancellationToken] = None) -> Dict[str, Any]:
# updates the ledger at each turn
max_json_retries = 10
team_description = await self._get_team_description()
names = await self._get_team_names()
ledger_prompt = self._get_ledger_prompt(self._task, team_description, names)
ledger_user_messages: List[LLMMessage] = [UserMessage(content=ledger_prompt, source=self.metadata["type"])]
# retries in case the LLM does not return a valid JSON
assert max_json_retries > 0
for _ in range(max_json_retries):
ledger_response = await self._model_client.create(
self._system_messages + self._chat_history + ledger_user_messages,
json_output=True,
cancellation_token=cancellation_token,
)
ledger_str = ledger_response.content
try:
assert isinstance(ledger_str, str)
ledger_dict: Dict[str, Any] = json.loads(ledger_str)
required_keys = [
"is_request_satisfied",
"is_in_loop",
"is_progress_being_made",
"next_speaker",
"instruction_or_question",
]
key_error = False
for key in required_keys:
if key not in ledger_dict:
ledger_user_messages.append(AssistantMessage(content=ledger_str, source="self"))
ledger_user_messages.append(
UserMessage(content=f"KeyError: '{key}'", source=self.metadata["type"])
)
key_error = True
break
if "answer" not in ledger_dict[key]:
ledger_user_messages.append(AssistantMessage(content=ledger_str, source="self"))
ledger_user_messages.append(
UserMessage(content=f"KeyError: '{key}.answer'", source=self.metadata["type"])
)
key_error = True
break
if key_error:
continue
return ledger_dict
except json.JSONDecodeError as e:
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (error)",
f"Failed to parse ledger information: {ledger_str}",
)
)
raise e
raise ValueError("Failed to parse ledger information after multiple retries.")
async def _prepare_final_answer(self, cancellation_token: Optional[CancellationToken] = None) -> str:
# called when the task is complete
final_message = UserMessage(
content=ORCHESTRATOR_GET_FINAL_ANSWER.format(task=self._task), source=self.metadata["type"]
)
response = await self._model_client.create(
self._system_messages + self._chat_history + [final_message], cancellation_token=cancellation_token
)
assert isinstance(response.content, str)
return response.content
async def _handle_broadcast(self, message: BroadcastMessage, ctx: MessageContext) -> None:
self._chat_history.append(message.content)
await super()._handle_broadcast(message, ctx)
async def _select_next_agent(
self, message: LLMMessage, cancellation_token: Optional[CancellationToken] = None
) -> Optional[AgentProxy]:
# the main orchestrator loop
# Check if the task is still unset, in which case this message contains the task string
if len(self._task) == 0:
await self._initialize_task(self._get_message_str(message), cancellation_token)
# At this point the task, plan and facts shouls all be set
assert len(self._task) > 0
assert len(self._facts) > 0
assert len(self._plan) > 0
assert len(self._team_description) > 0
# Send everyone the plan
synthesized_prompt = self._get_synthesize_prompt(
self._task, self._team_description, self._facts, self._plan
)
topic_id = TopicId("default", self.id.key)
await self.publish_message(
BroadcastMessage(content=UserMessage(content=synthesized_prompt, source=self.metadata["type"])),
topic_id=topic_id,
cancellation_token=cancellation_token,
)
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (thought)",
f"Initial plan:\n{synthesized_prompt}",
)
)
self._replan_counter = 0
self._stall_counter = 0
synthesized_message = AssistantMessage(content=synthesized_prompt, source=self.metadata["type"])
self._chat_history.append(synthesized_message)
# Answer from this synthesized message
return await self._select_next_agent(synthesized_message, cancellation_token)
# Orchestrate the next step
ledger_dict = await self.update_ledger(cancellation_token)
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (thought)",
f"Updated Ledger:\n{json.dumps(ledger_dict, indent=2)}",
)
)
# Task is complete
if ledger_dict["is_request_satisfied"]["answer"] is True:
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (thought)",
"Request satisfied.",
)
)
if self._return_final_answer:
# generate a final message to summarize the conversation
final_answer = await self._prepare_final_answer(cancellation_token)
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (final answer)",
f"\n{final_answer}",
)
)
return None
# Stalled or stuck in a loop
stalled = ledger_dict["is_in_loop"]["answer"] or not ledger_dict["is_progress_being_made"]["answer"]
if stalled:
self._stall_counter += 1
# We exceeded our stall counter, so we need to replan, or exit
if self._stall_counter > self._max_stalls_before_replan:
self._replan_counter += 1
self._stall_counter = 0
# We exceeded our replan counter
if self._replan_counter > self._max_replans:
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (thought)",
"Replan counter exceeded... Terminating.",
)
)
return None
# Let's create a new plan
else:
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (thought)",
"Stalled.... Replanning...",
)
)
# Update our plan.
await self._update_facts_and_plan(cancellation_token)
# Reset everyone, then rebroadcast the new plan
self._chat_history = [self._chat_history[0]]
topic_id = TopicId("default", self.id.key)
await self.publish_message(ResetMessage(), topic_id=topic_id, cancellation_token=cancellation_token)
# Send everyone the NEW plan
synthesized_prompt = self._get_synthesize_prompt(
self._task, self._team_description, self._facts, self._plan
)
await self.publish_message(
BroadcastMessage(content=UserMessage(content=synthesized_prompt, source=self.metadata["type"])),
topic_id=topic_id,
cancellation_token=cancellation_token,
)
self.logger.info(
OrchestrationEvent(
f"{self.metadata['type']} (thought)",
f"New plan:\n{synthesized_prompt}",
)
)
synthesized_message = AssistantMessage(content=synthesized_prompt, source=self.metadata["type"])
self._chat_history.append(synthesized_message)
# Answer from this synthesized message
return await self._select_next_agent(synthesized_message, cancellation_token)
# If we goit this far, we were not starting, done, or stuck
next_agent_name = ledger_dict["next_speaker"]["answer"]
# find the agent with the next agent name
for agent in self._agents:
if (await agent.metadata)["type"] == next_agent_name:
# broadcast a new message
instruction = ledger_dict["instruction_or_question"]["answer"]
user_message = UserMessage(content=instruction, source=self.metadata["type"])
assistant_message = AssistantMessage(content=instruction, source=self.metadata["type"])
self.logger.info(OrchestrationEvent(f"{self.metadata['type']} (-> {next_agent_name})", instruction))
self._chat_history.append(assistant_message) # My copy
topic_id = TopicId("default", self.id.key)
await self.publish_message(
BroadcastMessage(content=user_message, request_halt=False),
topic_id=topic_id,
cancellation_token=cancellation_token,
) # Send to everyone else
return agent
return None
import asyncio
from typing import Tuple
from autogen_core.base import CancellationToken
from autogen_core.components import default_subscription
from ..messages import UserContent
from .base_worker import BaseWorker
@default_subscription
class UserProxy(BaseWorker):
"""An agent that allows the user to play the role of an agent in the conversation via input."""
DEFAULT_DESCRIPTION = "A human user."
def __init__(
self,
description: str = DEFAULT_DESCRIPTION,
) -> None:
super().__init__(description)
async def _generate_reply(self, cancellation_token: CancellationToken) -> Tuple[bool, UserContent]:
"""Respond to a reply request."""
# Make an inference to the model.
response = await self.ainput("User input ('exit' to quit): ")
response = response.strip()
return response == "exit", response
async def ainput(self, prompt: str) -> str:
return await asyncio.to_thread(input, f"{prompt} ")
import os
import sys
import glob
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Updated imports
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_chroma.vectorstores import Chroma
from langchain_openai.llms import OpenAI
from langchain.chains import RetrievalQA
# Updated document loaders
from langchain_community.document_loaders import TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
def main():
# Load OpenAI API key
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
print("Please set your OPENAI_API_KEY in the .env file.")
sys.exit(1)
# Define the folder path (change 'data' to your folder name)
folder_path = './data'
if not os.path.exists(folder_path):
print(f"Folder '{folder_path}' does not exist.")
sys.exit(1)
# Read all files in the folder
documents = []
for filepath in glob.glob(os.path.join(folder_path, '**/*.*'), recursive=True):
if os.path.isfile(filepath):
ext = os.path.splitext(filepath)[1].lower()
try:
if ext == '.txt':
loader = TextLoader(filepath, encoding='utf-8')
documents.extend(loader.load_and_split())
elif ext == '.pdf':
loader = PyPDFLoader(filepath)
documents.extend(loader.load_and_split())
else:
print(f"Unsupported file format: {filepath}")
except Exception as e:
print(f"Error reading '{filepath}': {e}")
if not documents:
print("No documents found in the folder.")
sys.exit(1)
# Split documents into chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
texts = text_splitter.split_documents(documents)
# Initialize embeddings and vector store
embeddings = OpenAIEmbeddings()
vector_store = Chroma(embedding_function=embeddings, persist_directory="./chroma_store")
# Add texts to vector store in batches
batch_size = 500 # Adjust this number as needed
for i in range(0, len(texts), batch_size):
batch_texts = texts[i:i+batch_size]
vector_store.add_documents(batch_texts)
# Set up retriever
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
# Set up the language model
llm = OpenAI(temperature=0.7)
# Create the RetrievalQA chain
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # Options: 'stuff', 'map_reduce', 'refine', 'map_rerank'
retriever=retriever
)
# Interactive prompt for user queries
print("The system is ready. You can now ask questions about the content.")
while True:
query = input("Enter your question (or type 'exit' to quit): ")
if query.lower() in ('exit', 'quit'):
break
try:
response = qa_chain.run(query)
print(f"\nAnswer: {response}\n")
except Exception as e:
print(f"An error occurred: {e}\n")
if __name__ == "__main__":
main()
```
Let's break down each part of the code.
### 1. Loading Environment Variables
We use `python-dotenv` to load environment variables from a `.env` file. This is where we'll store our OpenAI API key securely.
```python
import os
import sys
from dotenv import load_dotenv
load_dotenv()
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
print("Please set your OPENAI_API_KEY in the .env file.")
sys.exit(1)
```
**Instructions:**
- Create a `.env` file in your project directory.
- Add your OpenAI API key:
```
OPENAI_API_KEY=your_openai_api_key_here
```
### 2. Importing Necessary Libraries
We import updated modules from `langchain` and associated packages.
```python
# Embeddings and vector store
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_chroma.vectorstores import Chroma
from langchain_openai.llms import OpenAI
from langchain.chains import RetrievalQA
# Document loaders and text splitter
from langchain_community.document_loaders import TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
```
**Note:** Ensure all packages are up-to-date to avoid deprecation warnings.
### 3. Loading and Splitting Documents
The script reads all `.txt` and `.pdf` files from the specified folder and splits them into manageable chunks.
```python
import glob
folder_path = './data'
if not os.path.exists(folder_path):
print(f"Folder '{folder_path}' does not exist.")
sys.exit(1)
documents = []
for filepath in glob.glob(os.path.join(folder_path, '**/*.*'), recursive=True):
if os.path.isfile(filepath):
ext = os.path.splitext(filepath)[1].lower()
try:
if ext == '.txt':
loader = TextLoader(filepath, encoding='utf-8')
documents.extend(loader.load_and_split())
elif ext == '.pdf':
loader = PyPDFLoader(filepath)
documents.extend(loader.load_and_split())
else:
print(f"Unsupported file format: {filepath}")
except Exception as e:
print(f"Error reading '{filepath}': {e}")
if not documents:
print("No documents found in the folder.")
sys.exit(1)
# Split documents into chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
texts = text_splitter.split_documents(documents)
From this code as reference build an agentic ai with RAG that interacts with openAI and allows you to create personas from writing samples. The persona is then passed to a new prompt or agent and this prompt will tell the agent to query the original prompt plus the instructions to imitate the analyzed style of the writing samples in all further communication. Thus you can save the persona and then recall all conversations with them using RAG and at the same time use the RAG to generate new interactions with the persona. Use the agents in this to help automate the output of the program which is simple a .md file
To build an agentic AI with Retrieval-Augmented Generation (RAG) that interacts with OpenAI and allows you to create personas from writing samples, we can follow these steps:
1. **Process Writing Samples and Create a Persona:**
- Load and preprocess the writing samples.
- Generate embeddings using OpenAI's embedding API.
- Store the embeddings in a vector store for efficient retrieval.
2. **Build the Retrieval Function:**
- Use the vector store to retrieve relevant pieces of the writing samples based on a query.
- This retrieval helps in maintaining the style and context of the persona.
3. **Create the Agent:**
- Develop an agent that uses the retrieved context to generate responses imitating the persona's style.
- Use OpenAI's `ChatCompletion` API with appropriate prompts.
4. **Automate Output to a Markdown (`.md`) File:**
- Save the generated interactions or responses to a markdown file.
- This allows you to recall conversations with the persona and generate new interactions.
5. **Combine Steps into a Cohesive Program:**
- Integrate all components into a unified script or application.
- Ensure the program can handle user inputs and generate outputs accordingly.
Below is a step-by-step guide with code examples to help you implement this system.
---
## **Step 1: Process Writing Samples and Create a Persona**
### **1.1 Load and Preprocess Writing Samples**
First, we need to load the writing samples from which we'll create the persona.
```python
import os
import glob
from langchain.document_loaders import TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Define the folder path containing the writing samples
folder_path = './writing_samples'
# Load documents
documents = []
for filepath in glob.glob(os.path.join(folder_path, '**/*.*'), recursive=True):
if os.path.isfile(filepath):
ext = os.path.splitext(filepath)[1].lower()
try:
if ext == '.txt':
loader = TextLoader(filepath, encoding='utf-8')
documents.extend(loader.load())
elif ext == '.pdf':
loader = PyPDFLoader(filepath)
documents.extend(loader.load())
else:
print(f"Unsupported file format: {filepath}")
except Exception as e:
print(f"Error reading '{filepath}': {e}")
if not documents:
print("No documents found in the folder.")
exit(1)
# Split documents into manageable chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
texts = text_splitter.split_documents(documents)
```
### **1.2 Generate Embeddings and Store in Vector Store**
We will use OpenAI's embedding model to generate embeddings and store them in a vector store like Chroma or FAISS.
```python
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
# Initialize OpenAI embeddings
embeddings = OpenAIEmbeddings()
# Create a Chroma vector store
vector_store = Chroma.from_documents(texts, embeddings, persist_directory="./persona_vectorstore")
# Persist the vector store for future use
vector_store.persist()
```
---
## **Step 2: Build the Retrieval Function**
We need to retrieve relevant writing samples when generating new responses to maintain the persona's style.
```python
# Set up the retriever
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
```
---
## **Step 3: Create the Agent**
We will create an agent that uses the retrieved context to generate responses imitating the persona's style.
### **3.1 Define the Agent**
```python
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
# Initialize the language model
llm = ChatOpenAI(temperature=0.7)
# Create the RetrievalQA chain
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # You can experiment with 'map_reduce', 'refine', etc.
retriever=retriever,
return_source_documents=True # To include source documents in the output
)
```
### **3.2 Customize the Prompt to Imitate the Persona**
We need to instruct the agent to imitate the analyzed style of the writing samples.
```python
from langchain.prompts import PromptTemplate
# Define a custom prompt template
persona_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""
You are an AI assistant imitating the writing style of a specific persona based on provided writing samples.
Context:
{context}
Question:
{question}
Answer in the persona's writing style.
"""
)
# Update the chain with the custom prompt
qa_chain.combine_documents_chain.llm_chain.prompt = persona_prompt
```
---
## **Step 4: Automate Output to a Markdown File**
We will save the interactions with the persona to a `.md` file.
```python
def save_to_markdown(conversation, filename="conversation.md"):
with open(filename, "a", encoding="utf-8") as f:
f.write(conversation + "\n\n")
```
---
## **Step 5: Combine Steps into a Cohesive Program**
Putting it all together, we can create a script that interacts with the user, generates responses imitating the persona, and saves the conversation to a markdown file.
```python
import os
import glob
from langchain.document_loaders import TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
def main():
# Load OpenAI API key
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
print("Please set your OPENAI_API_KEY in the environment variables.")
exit(1)
# Step 1: Load and process writing samples
folder_path = './writing_samples'
documents = []
for filepath in glob.glob(os.path.join(folder_path, '**/*.*'), recursive=True):
if os.path.isfile(filepath):
ext = os.path.splitext(filepath)[1].lower()
try:
if ext == '.txt':
loader = TextLoader(filepath, encoding='utf-8')
documents.extend(loader.load())
elif ext == '.pdf':
loader = PyPDFLoader(filepath)
documents.extend(loader.load())
else:
print(f"Unsupported file format: {filepath}")
except Exception as e:
print(f"Error reading '{filepath}': {e}")
if not documents:
print("No documents found in the folder.")
exit(1)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
texts = text_splitter.split_documents(documents)
# Step 2: Create embeddings and vector store
embeddings = OpenAIEmbeddings()
vector_store = Chroma.from_documents(texts, embeddings, persist_directory="./persona_vectorstore")
vector_store.persist()
# Step 3: Set up the retriever and agent
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
llm = ChatOpenAI(temperature=0.7)
persona_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""
You are an AI assistant imitating the writing style of a specific persona based on provided writing samples.
Context:
{context}
Question:
{question}
Answer in the persona's writing style.
"""
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=False,
chain_type_kwargs={"prompt": persona_prompt}
)
# Step 4: Interact with the user and save to markdown
print("You can now interact with the persona. Type 'exit' to quit.\n")
conversation_history = ""
while True:
user_input = input("You: ")
if user_input.lower() in ('exit', 'quit'):
break
# Generate response
response = qa_chain.run(user_input)
# Display and save the conversation
print(f"Persona: {response}\n")
conversation = f"### You:\n{user_input}\n\n### Persona:\n{response}"
save_to_markdown(conversation)
def save_to_markdown(conversation, filename="conversation.md"):
with open(filename, "a", encoding="utf-8") as f:
f.write(conversation + "\n\n---\n\n")
if __name__ == "__main__":
main()
```
---
## **Additional Considerations**
### **Using Agents to Automate Output**
If you wish to incorporate agents for more complex automation, such as handling tasks, scheduling, or processing, you can use LangChain's agent capabilities.
```python
from langchain.agents import initialize_agent, Tool
from langchain.agents.agent_types import AgentType
# Define a tool that uses the qa_chain
def persona_tool(query):
return qa_chain.run(query)
tools = [
Tool(
name="PersonaQA",
func=persona_tool,
description="Use this tool to get answers in the persona's style."
)
]
# Initialize the agent
agent = initialize_agent(
tools,
llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True
)
```
You can then interact with the agent:
```python
while True:
user_input = input("You: ")
if user_input.lower() in ('exit', 'quit'):
break
# Generate response using the agent
response = agent.run(user_input)
# Display and save the conversation
print(f"Persona: {response}\n")
conversation = f"### You:\n{user_input}\n\n### Persona:\n{response}"
save_to_markdown(conversation)
```
### **Enhancing the Persona**
- **Style Analysis:** For a more sophisticated imitation, you can analyze the writing samples for specific stylistic features and include those in your prompt.
- **Dynamic Prompts:** Modify the prompt template to include specific instructions or style guidelines derived from the writing samples.
---
## **Conclusion**
By following the steps above, you can build an agentic AI that interacts with OpenAI's APIs to create personas from writing samples. The system uses RAG to retrieve relevant context, allowing the AI to imitate the analyzed style in all further communication. The conversations are saved to a markdown file, enabling you to recall and generate new interactions with the persona.
---
## **Dependencies and Setup**
Ensure you have the following packages installed:
```bash
pip install openai langchain chromadb
```
Also, set your OpenAI API key as an environment variable or in a `.env` file:
```bash
export OPENAI_API_KEY=your_openai_api_key
```
Or in a `.env` file:
```env
OPENAI_API_KEY=your_openai_api_key
```
---
## **Notes**
- **OpenAI API Limits:** Be mindful of the rate limits and token usage in your OpenAI account.
- **Data Privacy:** Ensure that you have the rights to use the writing samples, and handle any personal data in compliance with privacy laws and regulations.
- **Error Handling:** The provided code is a basic implementation. In a production environment, include error handling, logging, and validation as needed.
- **Optimization:** Adjust parameters like `chunk_size`, `k` in `search_kwargs`, and `temperature` to optimize performance and response quality.
---
Let me know if you need further assistance or have any questions about implementing this system.
o1-preview
(vevn) daniel@Daniels-MacBook-Pro AgentPersona % python3 process.py
/Users/daniel/AgentPersona/process.py:46: LangChainDeprecationWarning: The class `OpenAIEmbeddings` was deprecated in LangChain 0.0.9 and will be removed in 1.0. An updated version of the class exists in the :class:`~langchain-openai package and should be used instead. To use it run `pip install -U :class:`~langchain-openai` and import as `from :class:`~langchain_openai import OpenAIEmbeddings``.
embeddings = OpenAIEmbeddings()
Traceback (most recent call last):
File "/Users/daniel/AgentPersona/process.py", line 98, in <module>
main()
File "/Users/daniel/AgentPersona/process.py", line 46, in main
embeddings = OpenAIEmbeddings()
^^^^^^^^^^^^^^^^^^
File "/Users/daniel/AgentPersona/vevn/lib/python3.11/site-packages/langchain_core/_api/deprecation.py", line 216, in warn_if_direct_instance
return wrapped(self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/daniel/AgentPersona/vevn/lib/python3.11/site-packages/pydantic/main.py", line 214, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for OpenAIEmbeddings
Value error, Did not find openai_api_key, please add an environment variable `OPENAI_API_KEY` which contains it, or pass `openai_api_key` as a named parameter. [type=value_error, input_value={'model_kwargs': {}, 'cli...20, 'http_client': None}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.10/v/value_error
The error you're encountering stems from two main issues:
1. **Deprecation Warning**: The `OpenAIEmbeddings` class from the `langchain` package is deprecated.
2. **Validation Error**: The `OpenAIEmbeddings` class cannot find your `OPENAI_API_KEY`.
Let's address both issues step-by-step to get your script running smoothly.
---
## **1. Addressing the Deprecation Warning**
### **1.1. Update the `langchain-openai` Package**
The `OpenAIEmbeddings` class has been moved to the `langchain-openai` package. First, ensure you have the latest version of this package installed.
**Run the following command:**
```bash
pip install -U langchain-openai
```
### **1.2. Update Import Statements**
Modify your import statements to use the updated `OpenAIEmbeddings` class from the `langchain_openai` package.
**Before:**
```python
from langchain.embeddings import OpenAIEmbeddings
```
**After:**
```python
from langchain_openai import OpenAIEmbeddings
```
---
## **2. Ensuring the OpenAI API Key is Properly Loaded**
The validation error indicates that the `OPENAI_API_KEY` environment variable isn't being recognized. Here's how to ensure it's correctly set and loaded.
### **2.1. Verify the `.env` File**
Ensure you have a `.env` file in your project's root directory with the following content:
```
OPENAI_API_KEY=your_openai_api_key_here
```
**Steps:**
1. **Locate the `.env` File:**
- It should be in the same directory from which you're running your script (`process.py`).
- If it's elsewhere, you'll need to specify its path in the `load_dotenv` function.
2. **Check the Content:**
- Ensure there's no whitespace around the `=` sign.
- Ensure there are no quotes around the API key unless they're part of the key itself.
### **2.2. Update the `process.py` Script**
Modify your `process.py` to use the updated `OpenAIEmbeddings` and ensure the API key is correctly passed.
**Updated `process.py`:**
```python
import os
import glob
import sys
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings # Updated import
from langchain.document_loaders import TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
def main():
# Load environment variables from .env file
load_dotenv() # Ensure this is called early
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
print("Error: OPENAI_API_KEY not found in environment variables.")
sys.exit(1)
# Define the folder path containing the writing samples
folder_path = './writing_samples'
# Load documents
documents = []
for filepath in glob.glob(os.path.join(folder_path, '**/*.*'), recursive=True):
if os.path.isfile(filepath):
ext = os.path.splitext(filepath)[1].lower()
try:
if ext == '.txt':
loader = TextLoader(filepath, encoding='utf-8')
documents.extend(loader.load())
elif ext == '.pdf':
loader = PyPDFLoader(filepath)
documents.extend(loader.load())
else:
print(f"Unsupported file format: {filepath}")
except Exception as e:
print(f"Error reading '{filepath}': {e}")
if not documents:
print("No documents found in the folder.")
sys.exit(1)
# Split documents into manageable chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
texts = text_splitter.split_documents(documents)
# Initialize embeddings and vector store
embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key) # Pass API key directly
vector_store = Chroma.from_documents(texts, embeddings, persist_directory="./chroma_store")
vector_store.persist()
# Set up retriever
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
# Set up the language model and RetrievalQA chain
llm = ChatOpenAI(temperature=0.7, openai_api_key=openai_api_key)
# Define a custom prompt template to imitate the persona's style
persona_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""
You are an AI assistant imitating the writing style of a specific persona based on provided writing samples.
Context:
{context}
Question:
{question}
Answer in the persona's writing style.
"""
)
# Create the RetrievalQA chain with the custom prompt
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=False,
chain_type_kwargs={"prompt": persona_prompt}
)
# Function to save conversations to a markdown file
def save_to_markdown(conversation, filename="conversation.md"):
with open(filename, "a", encoding="utf-8") as f:
f.write(conversation + "\n\n---\n\n")
# Interactive prompt for user queries
print("The system is ready. You can now ask questions about the content.")
while True:
query = input("Enter your question (or type 'exit' to quit): ")
if query.lower() in ('exit', 'quit'):
break
try:
response = qa_chain.run(query)
print(f"\nAnswer: {response}\n")
conversation = f"### You:\n{query}\n\n### Persona:\n{response}"
save_to_markdown(conversation)
except Exception as e:
print(f"An error occurred: {e}\n")
if __name__ == "__main__":
main()
```
### **2.3. Pass the API Key Directly to `ChatOpenAI` (Optional but Recommended)**
To ensure that the `ChatOpenAI` instance also recognizes your API key, pass it directly during initialization.
**Example:**
```python
llm = ChatOpenAI(temperature=0.7, openai_api_key=openai_api_key)
```
---
## **3. Complete Updated `process.py` Script**
Here's the full updated script incorporating all the necessary changes:
```python
import os
import glob
import sys
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings # Updated import
from langchain.document_loaders import TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
def main():
# Load environment variables from .env file
load_dotenv() # Ensure this is called early
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
print("Error: OPENAI_API_KEY not found in environment variables.")
sys.exit(1)
# Define the folder path containing the writing samples
folder_path = './writing_samples'
# Load documents
documents = []
for filepath in glob.glob(os.path.join(folder_path, '**/*.*'), recursive=True):
if os.path.isfile(filepath):
ext = os.path.splitext(filepath)[1].lower()
try:
if ext == '.txt':
loader = TextLoader(filepath, encoding='utf-8')
documents.extend(loader.load())
elif ext == '.pdf':
loader = PyPDFLoader(filepath)
documents.extend(loader.load())
else:
print(f"Unsupported file format: {filepath}")
except Exception as e:
print(f"Error reading '{filepath}': {e}")
if not documents:
print("No documents found in the folder.")
sys.exit(1)
# Split documents into manageable chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
texts = text_splitter.split_documents(documents)
# Initialize embeddings and vector store
embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key) # Pass API key directly
vector_store = Chroma.from_documents(texts, embeddings, persist_directory="./chroma_store")
vector_store.persist()
# Set up retriever
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
# Set up the language model and RetrievalQA chain
llm = ChatOpenAI(temperature=0.7, openai_api_key=openai_api_key)
# Define a custom prompt template to imitate the persona's style
persona_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""
You are an AI assistant imitating the writing style of a specific persona based on provided writing samples.
Context:
{context}
Question:
{question}
Answer in the persona's writing style.
"""
)
# Create the RetrievalQA chain with the custom prompt
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=False,
chain_type_kwargs={"prompt": persona_prompt}
)
# Function to save conversations to a markdown file
def save_to_markdown(conversation, filename="conversation.md"):
with open(filename, "a", encoding="utf-8") as f:
f.write(conversation + "\n\n---\n\n")
# Interactive prompt for user queries
print("The system is ready. You can now ask questions about the content.")
while True:
query = input("Enter your question (or type 'exit' to quit): ")
if query.lower() in ('exit', 'quit'):
break
try:
response = qa_chain.run(query)
print(f"\nAnswer: {response}\n")
conversation = f"### You:\n{query}\n\n### Persona:\n{response}"
save_to_markdown(conversation)
except Exception as e:
print(f"An error occurred: {e}\n")
def save_to_markdown(conversation, filename="conversation.md"):
with open(filename, "a", encoding="utf-8") as f:
f.write(conversation + "\n\n---\n\n")
if __name__ == "__main__":
main()
```
---
## **4. Additional Troubleshooting Steps**
If after making the above changes you still encounter issues, follow these steps to further diagnose and resolve the problem.
### **4.1. Verify the Environment Variable is Loaded**
Add a temporary print statement to ensure that the `OPENAI_API_KEY` is being loaded correctly.
```python
print(f"OpenAI API Key: {openai_api_key}") # Add this line for debugging
```
**Note:** **Remove this line after verification** to prevent exposing your API key.
### **4.2. Check the `.env` File Location**
Ensure that the `.env` file is in the same directory from which you're running the script or specify its path explicitly.
**Specify Path Explicitly:**
```python
load_dotenv(dotenv_path='/full/path/to/your/.env')
```
### **4.3. Confirm Environment Variable in Terminal**
Before running the script, check if the environment variable is set in your terminal.
```bash
echo $OPENAI_API_KEY
```
It should output your API key. If it doesn't, ensure that the `.env` file is correctly formatted and located.
### **4.4. Pass the API Key Directly (Alternative Approach)**
If environment variables are not working as expected, pass the API key directly to the `OpenAIEmbeddings` and `ChatOpenAI` classes.
```python
embeddings = OpenAIEmbeddings(openai_api_key="your_openai_api_key_here")
llm = ChatOpenAI(temperature=0.7, openai_api_key="your_openai_api_key_here")
```
**Note:** Be cautious with this approach to avoid hardcoding sensitive information. Prefer using environment variables in production environments.
### **4.5. Check Package Versions**
Ensure all packages are up-to-date to avoid compatibility issues.
```bash
pip install -U langchain langchain-openai chromadb openai
```
### **4.6. Ensure Correct Directory Structure**
Make sure your project directory looks something like this:
```
AgentPersona/
├── .env
├── process.py
├── writing_samples/
│ ├── sample1.txt
│ ├── sample2.pdf
│ └── ...
├── chroma_store/
│ └── ...
├── conversation.md
└── venv/
└── ...
```
- `.env` should be in the root directory (`AgentPersona/`).
- `writing_samples/` should contain your `.txt` and `.pdf` writing samples.
- `chroma_store/` is where the vector store will be persisted.
- `conversation.md` will store your interactions.
### **4.7. Example `.env` File**
Ensure your `.env` file is correctly formatted. Here's an example:
```
OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```
**Important:** Replace `sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` with your actual OpenAI API key.
---
## **5. Final Steps**
After making all the above changes, perform the following steps to ensure everything is working correctly.
### **5.1. Restart Your Virtual Environment**
If you're using a virtual environment, deactivate and reactivate it to ensure all changes are applied.
```bash
# Deactivate the current virtual environment
deactivate
# Reactivate the virtual environment
source venv/bin/activate
```
### **5.2. Run the Script Again**
Execute your script to verify that it runs without errors.
```bash
python3 process.py
```
### **5.3. Test the Interaction**
Once the script is running, you should see:
```
The system is ready. You can now ask questions about the content.
Enter your question (or type 'exit' to quit):
```
Try asking a question related to your writing samples. For example:
```
Enter your question (or type 'exit' to quit): What is the main theme of the provided texts?
```
You should receive a response imitating the style of the persona derived from your writing samples.
---
## **6. Additional Enhancements**
To further enhance your agentic AI system, consider the following:
### **6.1. Implementing Personas with Detailed Attributes**
Instead of just imitating the style, you can enrich the persona with additional attributes like personality traits, preferences, and more. Modify the prompt template to include these details.
**Example:**
```python
persona_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""
You are an AI assistant imitating the writing style and personality of a specific persona based on provided writing samples.
Context:
{context}
Question:
{question}
Answer in the persona's writing style, reflecting their personality and preferences.
"""
)
```
### **6.2. Managing Multiple Personas**
If you want to handle multiple personas, consider organizing your vector stores and prompts accordingly. You can create separate vector stores for each persona and switch between them based on user input.
### **6.3. Enhancing Retrieval Strategies**
Experiment with different retrieval strategies (`chain_type` options) like `map_reduce` or `refine` to improve the quality of the responses.
### **6.4. Logging and Monitoring**
Implement logging to monitor the interactions and performance of your agentic AI system. This will help in debugging and improving the system over time.
**Example:**
```python
import logging
# Configure logging
logging.basicConfig(filename='agent.log', level=logging.INFO,
format='%(asctime)s:%(levelname)s:%(message)s')
# Log each interaction
def save_to_markdown(conversation, filename="conversation.md"):
with open(filename, "a", encoding="utf-8") as f:
f.write(conversation + "\n\n---\n\n")
logging.info(conversation)
```
### **6.5. Error Handling**
Implement more robust error handling to manage potential issues gracefully.
**Example:**
```python
try:
response = qa_chain.run(query)
print(f"\nAnswer: {response}\n")
conversation = f"### You:\n{query}\n\n### Persona:\n{response}"
save_to_markdown(conversation)
except Exception as e:
print(f"An error occurred: {e}\n")
logging.error(f"Error processing query '{query}': {e}")
```
---
## **Conclusion**
By updating your import statements, ensuring the OpenAI API key is correctly loaded, and following the provided updated script, you should be able to resolve the errors and successfully run your agentic AI system with RAG. This system will allow you to create personas from writing samples, interact with them, and save the conversations to a markdown file for future reference.
If you encounter further issues, please provide additional error messages or context, and I'll be happy to assist you further.
---
**Remember:** Always keep your API keys secure. Avoid hardcoding them in your scripts, especially if you're sharing your code publicly.
o1-mini
import os
import sys
import glob
from langchain_community.document_loaders import TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
import openai
from langchain_openai import OpenAIEmbeddings # Updated import
def main():
openai_api_key = "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"
# Step 1: Load and process writing samples
folder_path = './writing_samples'
documents = []
for filepath in glob.glob(os.path.join(folder_path, '**/*.*'), recursive=True):
if os.path.isfile(filepath):
ext = os.path.splitext(filepath)[1].lower()
try:
if ext == '.txt':
loader = TextLoader(filepath, encoding='utf-8')
documents.extend(loader.load())
elif ext == '.pdf':
loader = PyPDFLoader(filepath)
documents.extend(loader.load())
else:
print(f"Unsupported file format: {filepath}")
except Exception as e:
print(f"Error reading '{filepath}': {e}")
if not documents:
print("No documents found in the folder.")
exit(1)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
texts = text_splitter.split_documents(documents)
# Step 2: Create embeddings and vector store
embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key) # Pass API key directly
vector_store = Chroma.from_documents(texts, embeddings, persist_directory="./persona_vectorstore")
vector_store.persist()
# Step 3: Set up the retriever and agent
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
llm = ChatOpenAI(temperature=0.7, openai_api_key=openai_api_key)
persona_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""
You are an AI assistant imitating the writing style of a specific persona based on provided writing samples.
Context:
{context}
Question:
{question}
Answer in the persona's writing style.
"""
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=False,
chain_type_kwargs={"prompt": persona_prompt}
)
def save_to_markdown(conversation, filename="conversation.md"):
with open(filename, "a", encoding="utf-8") as f:
f.write(conversation + "\n\n---\n\n")
# Step 4: Interact with the user and save to markdown
print("You can now interact with the persona. Type 'exit' to quit.\n")
conversation_history = ""
while True:
user_input = input("You: ")
if user_input.lower() in ('exit', 'quit'):
break
# Generate response
response = qa_chain.run(user_input)
# Display and save the conversation
print(f"Persona: {response}\n")
conversation = f"### You:\n{user_input}\n\n### Persona:\n{response}"
save_to_markdown(conversation)
if __name__ == "__main__":
main()
Your script is well-structured for creating a persona-based conversational agent using writing samples. Here are a few important points and recommendations for further refinement and optimization:
---
### **1. API Key Security**
Your OpenAI API key is hardcoded in the script, which is not recommended. Hardcoding keys is a security risk, especially if the script is shared or pushed to a public repository. Instead, use environment variables or a `.env` file.
#### **Using Environment Variables**
1. Create a `.env` file in your project directory:
```plaintext
OPENAI_API_KEY=sk-your-api-key-here
```
2. Install `python-dotenv` if not already installed:
```bash
pip install python-dotenv
```
3. Update your script:
```python
from dotenv import load_dotenv
load_dotenv() # Load variables from .env
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
print("Error: OPENAI_API_KEY not found in environment variables.")
exit(1)
```
---
### **2. Dependency Cleanup**
Your script imports `OpenAIEmbeddings` twice, once from `langchain_openai` and once from `langchain_community`. Remove the redundant import.
```python
from langchain_openai import OpenAIEmbeddings # Correct import
```
Remove:
```python
from langchain_community.embeddings import OpenAIEmbeddings
```
---
### **3. Error Handling**
Add error handling to gracefully handle unexpected scenarios like:
- Empty or invalid user input.
- Retrieval or LLM API failures.
- File read errors.
#### Example:
```python
try:
response = qa_chain.run(user_input)
if not response.strip():
raise ValueError("Empty response from the persona.")
except Exception as e:
print(f"Error: {e}")
continue
```
---
### **4. Logging for Debugging**
Implement logging to keep track of errors and user interactions.
#### Example:
```python
import logging
# Configure logging
logging.basicConfig(filename="agent.log", level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s")
# Log interactions
logging.info(f"User: {user_input}")
logging.info(f"Persona: {response}")
```
---
### **5. Enhancements for Interaction**
#### **5.1 Contextual Continuity**
Currently, the agent generates responses without retaining prior context. Use a conversational memory mechanism to provide context continuity.
LangChain's `ConversationBufferMemory` can be integrated:
```python
from langchain.memory import ConversationBufferMemory
# Initialize memory
memory = ConversationBufferMemory(memory_key="history", return_messages=True)
# Update qa_chain to use memory
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
memory=memory,
return_source_documents=False,
chain_type_kwargs={"prompt": persona_prompt}
)
```
---
#### **5.2 Multiple Personas**
Support for multiple personas can be added by creating separate vector stores or switching embeddings dynamically based on user selection.
#### Example:
```python
persona_choice = input("Choose a persona (e.g., Author1, Author2): ")
vector_store_path = f"./persona_vectorstore_{persona_choice.lower()}"
vector_store = Chroma(persist_directory=vector_store_path, embedding_function=embeddings)
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
```
---
### **6. Performance Optimization**
#### **6.1 Chunk Size**
The `chunk_size` and `chunk_overlap` in `RecursiveCharacterTextSplitter` significantly impact retrieval performance. Adjust these values based on the average size of your documents.
#### **6.2 Batch Processing**
If your corpus is large, process texts in batches to improve efficiency.
---
### **7. Test Your Script**
Run your script and verify:
1. Documents are correctly loaded and split.
2. Embeddings are generated and persisted.
3. The agent responds in the desired style.
4. The interaction is logged correctly.
5. Conversations are saved to the markdown file.
---
### **Final Notes**
After implementing these changes, your script will be more secure, robust, and user-friendly. Let me know if you encounter any specific issues or need further enhancements!
gpt-4o
(venv) daniel@Daniels-MacBook-Pro PersonaChat02 % python3 chat.py
Traceback (most recent call last):
File "/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/core/__init__.py", line 24, in <module>
from . import multiarray
File "/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/core/multiarray.py", line 10, in <module>
from . import overrides
File "/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/core/overrides.py", line 8, in <module>
from numpy.core._multiarray_umath import (
ImportError: dlopen(/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/core/_multiarray_umath.cpython-311-darwin.so, 0x0002): Library not loaded: '@loader_path/../.dylibs/libopenblas64_.0.dylib'
Referenced from: '/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/core/_multiarray_umath.cpython-311-darwin.so'
Reason: tried: '/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/core/../.dylibs/libopenblas64_.0.dylib' (code signature invalid (errno=85) sliceOffset=0x00000000, codeBlobOffset=0x04147FD0, codeBlobSize=0x000D8A80 for '/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/.dylibs/libopenblas64_.0.dylib'), '/usr/lib/libopenblas64_.0.dylib' (no such file)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/__init__.py", line 130, in <module>
from numpy.__config__ import show as show_config
File "/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/__config__.py", line 4, in <module>
from numpy.core._multiarray_umath import (
File "/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/core/__init__.py", line 50, in <module>
raise ImportError(msg)
ImportError:
IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!
Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.
We have compiled some common reasons and troubleshooting tips at:
https://numpy.org/devdocs/user/troubleshooting-importerror.html
Please note and check the following:
* The Python version is: Python3.11 from "/Users/daniel/PersonaChat02/venv/bin/python3"
* The NumPy version is: "1.26.4"
and make sure that they are the versions you expect.
Please carefully study the documentation linked above for further help.
Original error was: dlopen(/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/core/_multiarray_umath.cpython-311-darwin.so, 0x0002): Library not loaded: '@loader_path/../.dylibs/libopenblas64_.0.dylib'
Referenced from: '/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/core/_multiarray_umath.cpython-311-darwin.so'
Reason: tried: '/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/core/../.dylibs/libopenblas64_.0.dylib' (code signature invalid (errno=85) sliceOffset=0x00000000, codeBlobOffset=0x04147FD0, codeBlobSize=0x000D8A80 for '/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/.dylibs/libopenblas64_.0.dylib'), '/usr/lib/libopenblas64_.0.dylib' (no such file)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/daniel/PersonaChat02/chat.py", line 4, in <module>
from langchain_community.document_loaders import TextLoader, PyPDFLoader
File "/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/langchain_community/document_loaders/__init__.py", line 734, in __getattr__
module = importlib.import_module(_module_lookup[name])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/langchain_community/document_loaders/pdf.py", line 30, in <module>
from langchain_community.document_loaders.parsers.pdf import (
File "/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/langchain_community/document_loaders/parsers/pdf.py", line 19, in <module>
import numpy as np
File "/Users/daniel/PersonaChat02/venv/lib/python3.11/site-packages/numpy/__init__.py", line 135, in <module>
raise ImportError(msg) from e
ImportError: Error importing numpy: you should not try to import numpy from
its source directory; please exit the numpy source tree, and relaunch
your python interpreter from there.
The error occurs because the `numpy` package is failing to load its necessary C extensions (`_multiarray_umath`), which rely on the `libopenblas64_.0.dylib` library. This issue commonly arises from misconfigured installations or corrupted binary dependencies.
Here's how you can troubleshoot and resolve this issue:
---
### **1. Check for Corrupted or Conflicting Dependencies**
The error suggests that the `numpy` package might have been corrupted during installation.
#### **Steps:**
1. **Uninstall and Reinstall `numpy`:**
```bash
pip uninstall numpy
pip install numpy
```
2. **Verify Installation:**
After reinstalling, check if `numpy` loads without errors:
```bash
python -c "import numpy; print(numpy.__version__)"
```
---
### **2. Reinstall Dependencies in a Fresh Virtual Environment**
Sometimes, issues occur due to conflicts within the virtual environment.
#### **Steps:**
1. **Delete Existing Virtual Environment:**
```bash
deactivate # Deactivate the current environment if active
rm -rf venv
```
2. **Create a New Virtual Environment:**
```bash
python3 -m venv venv
source venv/bin/activate
```
3. **Install Dependencies:**
Reinstall all your dependencies in the new environment:
```bash
pip install --upgrade pip
pip install -r requirements.txt
```
---
### **3. Install `numpy` with OpenBLAS Support**
The error suggests a problem with `libopenblas64_.0.dylib`. Reinstalling `numpy` with OpenBLAS explicitly should fix this.
#### **Steps:**
1. **Uninstall Existing `numpy`:**
```bash
pip uninstall numpy
```
2. **Reinstall with OpenBLAS:**
```bash
pip install numpy --no-binary=:all:
```
This ensures `numpy` compiles locally and links correctly to the appropriate BLAS library.
---
### **4. Check macOS Security Settings (Code Signature Error)**
The error mentions `code signature invalid`, which can occur if macOS blocks the `.dylib` due to security restrictions.
#### **Steps:**
1. Open `System Preferences > Security & Privacy`.
2. Check if there's a blocked file under the "General" tab.
3. If prompted, allow the file.
---
### **5. Use Conda for Dependency Management (Alternative)**
If the above solutions don’t work, using `conda` is a reliable alternative as it handles dependencies and library linking effectively.
#### **Steps:**
1. Install Miniconda or Anaconda if not already installed:
[Miniconda Installation](https://docs.conda.io/en/latest/miniconda.html)
2. Create a New Conda Environment:
```bash
conda create -n persona_chat python=3.11 numpy
conda activate persona_chat
```
3. Install Required Packages:
```bash
pip install langchain langchain-openai
```
---
### **6. Debugging Environment**
To ensure that your environment is set up correctly:
1. **List Installed Packages:**
```bash
pip list
```
Ensure `numpy` is listed without any warnings or errors.
2. **Verify BLAS Library Linking:**
Run the following to check if the correct BLAS library is linked:
```bash
python -c "import numpy; numpy.show_config()"
```
---
### **7. Final Verification**
Run your script again:
```bash
python3 chat.py
```
If the issue persists, share the output of:
1. `pip list`
2. `python -c "import numpy; numpy.show_config()"`
This will help diagnose any remaining configuration issues.
gpt-4o
write the readme.md for using this : import os
import sys
import glob
from langchain_community.document_loaders import TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
import openai
from langchain_openai import OpenAIEmbeddings # Updated import
from dotenv import load_dotenv
from langchain.memory import ConversationBufferMemory
load_dotenv() # Load variables from .env
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
print("Error: OPENAI_API_KEY not found in environment variables.")
exit(1)
def main():
# Initialize memory
memory = ConversationBufferMemory(memory_key="history", return_messages=True)
# Step 1: Load and process writing samples
folder_path = './writing_samples'
documents = []
for filepath in glob.glob(os.path.join(folder_path, '**/*.*'), recursive=True):
if os.path.isfile(filepath):
ext = os.path.splitext(filepath)[1].lower()
try:
if ext == '.txt':
loader = TextLoader(filepath, encoding='utf-8')
documents.extend(loader.load())
elif ext == '.pdf':
loader = PyPDFLoader(filepath)
documents.extend(loader.load())
else:
print(f"Unsupported file format: {filepath}")
except Exception as e:
print(f"Error reading '{filepath}': {e}")
if not documents:
print("No documents found in the folder.")
exit(1)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
texts = text_splitter.split_documents(documents)
# Step 2: Create embeddings and vector store
embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key) # Pass API key directly
vector_store = Chroma.from_documents(texts, embeddings, persist_directory="./persona_vectorstore")
vector_store.persist()
# Step 3: Set up the retriever and agent
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
llm = ChatOpenAI(temperature=0.7, openai_api_key=openai_api_key)
persona_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""
You are an AI assistant imitating the writing style of a specific persona based on provided writing samples.
Context:
{context}
Question:
{question}
Answer in the persona's writing style.
"""
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
memory=memory,
return_source_documents=False,
chain_type_kwargs={"prompt": persona_prompt}
)
def save_to_markdown(conversation, filename="conversation.md"):
with open(filename, "a", encoding="utf-8") as f:
f.write(conversation + "\n\n---\n\n")
# Step 4: Interact with the user and save to markdown
print("You can now interact with the persona. Type 'exit' to quit.\n")
conversation_history = ""
while True:
user_input = input("You: ")
if user_input.lower() in ('exit', 'quit'):
break
# Generate response
response = qa_chain.run(user_input)
# Display and save the conversation
print(f"Persona: {response}\n")
conversation = f"### You:\n{user_input}\n\n### Persona:\n{response}"
save_to_markdown(conversation)
if __name__ == "__main__":
main() --requirements.txt: openai
ollama
swarm
autogen
python-dotenv
flask
langchain
chromadb
pinecone-client
tiktoken
sentence-transformers
PyPDF2
langchain-community
langchain-openai
langchain-chroma
pypdf
# PersonaChat: AI-Powered Persona Interaction
PersonaChat is a Python application that creates an AI assistant capable of imitating the writing style of a specific persona based on user-provided writing samples. The system uses OpenAI's GPT and integrates retrieval-augmented generation (RAG) for enhanced contextual responses. Conversations are logged and saved as markdown files for later review.
---
## Features
- **Persona Imitation**: Analyze writing samples to generate AI responses in the style of a specific persona.
- **Retrieval-Augmented Generation**: Query and retrieve relevant context from user-provided documents.
- **Interactive Conversations**: Engage in a live chat with the persona.
- **Conversation History**: Save all interactions to a markdown file.
---
## Requirements
Ensure you have the following tools and libraries installed:
- Python 3.8 or higher
- Required Python packages (see `requirements.txt`):
- `openai`
- `ollama`
- `swarm`
- `autogen`
- `python-dotenv`
- `flask`
- `langchain`
- `chromadb`
- `pinecone-client`
- `tiktoken`
- `sentence-transformers`
- `PyPDF2`
- `langchain-community`
- `langchain-openai`
- `langchain-chroma`
- `pypdf`
---
## Setup
### 1. Clone the Repository
```bash
git clone https://github.com/yourusername/personachat.git
cd personachat
```
### 2. Create and Activate a Virtual Environment
```bash
python3 -m venv venv
source venv/bin/activate # On Windows, use: venv\Scripts\activate
```
### 3. Install Dependencies
```bash
pip install -r requirements.txt
```
### 4. Set Up the OpenAI API Key
Create a `.env` file in the project root and add your OpenAI API key:
```plaintext
OPENAI_API_KEY=your_openai_api_key_here
```
---
## Usage
### 1. Prepare Writing Samples
Place `.txt` and `.pdf` files containing the persona's writing style in the `writing_samples` folder.
### 2. Run the Application
Start the application with:
```bash
python chat.py
```
### 3. Interact with the Persona
- Type your questions or prompts to chat with the persona.
- Type `exit` or `quit` to end the conversation.
### 4. View Conversation History
Conversations are saved in `conversation.md` in markdown format. Open this file to review past interactions.
---
## Folder Structure
```
PersonaChat/
├── writing_samples/ # Folder for input files (writing samples)
├── persona_vectorstore/ # Folder for storing vectorized document data
├── conversation.md # File to save conversation logs
├── chat.py # Main application script
├── requirements.txt # List of dependencies
├── .env # Environment variables (e.g., API keys)
├── README.md # Documentation
```
---
## How It Works
1. **Writing Samples Analysis**:
- The program processes `.txt` and `.pdf` files using `langchain-community`.
- Documents are split into manageable chunks for analysis.
2. **Vector Store Creation**:
- Embeddings are generated using OpenAI's API.
- Chunks are stored in a persistent vector store powered by Chroma.
3. **Chat Interaction**:
- A retriever queries the vector store for context.
- OpenAI's GPT model generates responses imitating the persona's style.
- Responses and user inputs are saved in a markdown file.
4. **Memory**:
- A `ConversationBufferMemory` ensures context continuity during the chat session.
---
## Customization
### Adjust Prompt Template
You can modify the `persona_prompt` in `chat.py` to fine-tune the persona's behavior.
```python
persona_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""
You are an AI assistant imitating the writing style of a specific persona based on provided writing samples.
Context:
{context}
Question:
{question}
Answer in the persona's writing style.
"""
)
```
---
## Troubleshooting
### Common Issues
1. **Missing API Key**:
- Ensure your `.env` file is correctly configured with the `OPENAI_API_KEY`.
2. **Dependencies Not Installed**:
- Run `pip install -r requirements.txt` to install all required libraries.
3. **No Documents Found**:
- Verify that `.txt` or `.pdf` files are placed in the `writing_samples` folder.
4. **Error Importing Libraries**:
- Ensure the virtual environment is activated and dependencies are correctly installed.
---
## Future Enhancements
- **Multiple Personas**: Support for switching between multiple personas dynamically.
- **Web Interface**: Add a Flask-based UI for easier interaction.
- **Advanced Logging**: Store conversation history in a database.
---
## License
This project is licensed under the MIT License. See `LICENSE` for details.
---
## Contributing
Contributions are welcome! Please open an issue or submit a pull request for any improvements.
---
### Author
Daniel Kliewer
[GitHub](https://github.com/yourusername) | [Website](https://danielkliewer.com)
Enjoy using PersonaChat!
gpt-4o
no that does not seem right, make sure it only lists the dependencies needed
# PersonaChat: AI-Powered Persona Interaction
PersonaChat is a Python application that creates an AI assistant capable of imitating the writing style of a specific persona based on user-provided writing samples. The system uses OpenAI's GPT model with retrieval-augmented generation (RAG) to provide contextual responses.
---
## Features
- **Persona Imitation**: Generate AI responses in the style of a specific persona based on writing samples.
- **Retrieval-Augmented Generation**: Use RAG to retrieve relevant content from documents for enhanced responses.
- **Interactive Conversations**: Engage in live chat with the persona.
- **Conversation Logging**: Save all interactions to a markdown file for review.
---
## Requirements
### Dependencies
The required Python libraries are listed below:
- `openai`
- `python-dotenv`
- `langchain`
- `langchain-openai`
- `langchain-chroma`
- `chromadb`
- `sentence-transformers`
- `PyPDF2`
### Python Version
- Python 3.8 or higher
---
## Setup
### 1. Clone the Repository
```bash
git clone https://github.com/yourusername/personachat.git
cd personachat
```
### 2. Create and Activate a Virtual Environment
```bash
python3 -m venv venv
source venv/bin/activate # On Windows, use: venv\Scripts\activate
```
### 3. Install Dependencies
```bash
pip install -r requirements.txt
```
### 4. Set Up the OpenAI API Key
Create a `.env` file in the project root directory and add your OpenAI API key:
```plaintext
OPENAI_API_KEY=your_openai_api_key_here
```
---
## Usage
### 1. Prepare Writing Samples
Place `.txt` and `.pdf` files containing writing samples in the `writing_samples` directory.
### 2. Run the Application
Start the chat interface:
```bash
python chat.py
```
### 3. Interact with the Persona
- Type your questions or prompts to chat with the persona.
- Type `exit` or `quit` to end the conversation.
### 4. View Conversation Logs
All interactions are saved in `conversation.md`. Open the file to review your chat history.
---
## How It Works
1. **Load Writing Samples**:
- Reads `.txt` and `.pdf` files from the `writing_samples` directory.
- Splits documents into smaller chunks for processing.
2. **Create Embeddings**:
- Generates embeddings using OpenAI's embedding model.
- Stores embeddings in a persistent Chroma vector store.
3. **Query and Respond**:
- Uses a retriever to fetch relevant content from the vector store.
- Generates persona-style responses with GPT using the retrieved context.
4. **Log Conversations**:
- Saves user inputs and persona responses to `conversation.md` in markdown format.
---
## Folder Structure
```
PersonaChat/
├── writing_samples/ # Folder for input files (writing samples)
├── persona_vectorstore/ # Folder for storing vectorized document data
├── conversation.md # File to save conversation logs
├── chat.py # Main application script
├── requirements.txt # List of dependencies
├── .env # Environment variables (e.g., API keys)
├── README.md # Documentation
```
---
## Troubleshooting
### Common Issues
1. **Missing API Key**:
- Ensure your `.env` file contains the `OPENAI_API_KEY`.
2. **No Writing Samples Found**:
- Verify that `.txt` or `.pdf` files are placed in the `writing_samples` directory.
3. **Error Importing Libraries**:
- Ensure the virtual environment is activated and dependencies are installed using `pip install -r requirements.txt`.
---
## Example `.env` File
```plaintext
OPENAI_API_KEY=sk-your-api-key
```
---
## License
This project is licensed under the MIT License.
---
## Contributing
Contributions are welcome! Submit a pull request or open an issue to suggest improvements.
gpt-4o
Hey I just made this today:
https://github.com/kliewerdaniel/PersonaChat
You know how you can only include so much data in your prompts you enter into chatGPT? Well in this program I use the langchain library to vectorize txt or pdf text files and allow you to chat with a persona based on your documents.
One thing I did was input a book or journal into it and you can talk to a past version of yourself.
Or you could input the complete works of your favorite author and have a conversation about whatever you want.
I think the next iteration of this will allow the user to create multiple personas and have a complex conversation with multiple perspectives much like an online conversation.
Except now you can have conversations with figures from literature rather than some random social media commenter.
That sounds like an incredible project! The ability to create a conversational AI that can engage as different personas based on text or literary works is not just practical—it’s creatively inspiring. Here's a way to refine your pitch and highlight the uniqueness of your project:
---
## Introducing PersonaChat: Talk to Personas from Your Favorite Texts!
**GitHub Repository**: [PersonaChat](https://github.com/kliewerdaniel/PersonaChat)
Have you ever wanted to chat with your past self, your favorite author, or even a fictional character? PersonaChat makes it possible. Using the power of OpenAI's GPT and the LangChain library, this program brings text-based personas to life.
---
### What PersonaChat Can Do:
1. **Transform Documents into Conversations**:
- Feed it your journals or letters to chat with a past version of yourself.
- Input the complete works of an author like Jane Austen or Mark Twain and have an insightful conversation about their stories or philosophies.
2. **Deep, Contextual Conversations**:
- By vectorizing `.txt` or `.pdf` documents, PersonaChat retrieves the most relevant context for every interaction, allowing the persona to stay consistent and informed.
3. **Extend Beyond Social Media**:
- Instead of chatting with random social media commenters, engage in meaningful dialogues with literary figures, historical personas, or even your own fictional creations.
---
### A Glimpse Into the Future:
The next iteration of PersonaChat will support **multiple personas**, enabling:
- Complex, multi-perspective discussions.
- Simulating literary debates or dialogues with multiple characters.
- Collaborative brainstorming with personas inspired by different thinkers or authors.
Imagine discussing politics with George Orwell and Plato, or having Virginia Woolf and Ernest Hemingway critique your writing—all in a single conversation!
---
### Why PersonaChat Stands Out:
- **Accessible to Everyone**:
- Just add your `.txt` or `.pdf` files and start chatting.
- **Versatile Use Cases**:
- Ideal for writers, researchers, educators, and anyone who loves literature or self-reflection.
- **Open-Source & Extensible**:
- Built to evolve with community contributions and feedback.
---
### Get Started:
Check out the GitHub repository for setup instructions: [PersonaChat on GitHub](https://github.com/kliewerdaniel/PersonaChat).
Let me know what you think of PersonaChat, and feel free to contribute or share your ideas for future features!
gpt-4o
is there a way to export a code base in vscode to pdf
Yes, you can export a code base in VS Code to a PDF. Here are a few methods you can use:
---
### **Method 1: Use an Extension**
1. **Install the "Markdown PDF" Extension**:
- Go to the Extensions Marketplace in VS Code.
- Search for "Markdown PDF" or a similar extension that allows exporting code or documents as PDFs.
- Install it.
2. **Export Individual Files**:
- Open the file you want to export.
- Use the command palette (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS) to find the export command, e.g., `Markdown PDF: Export`.
- Choose PDF as the export format.
3. **Batch Export**:
- Check the extension's documentation to see if it supports batch exporting multiple files.
---
### **Method 2: Print to PDF**
1. **Open File in VS Code**:
- Open the file you'd like to export in the editor.
2. **Copy Code into a Word Processor**:
- Highlight the code and copy it into a document editor like Microsoft Word or Google Docs.
- Use a monospace font like "Courier New" or "Consolas" for better formatting.
3. **Print to PDF**:
- In your document editor, go to `File > Print` and choose "Save as PDF" or "Export as PDF."
---
### **Method 3: Use a PDF Generator**
If you want to export the entire code base (multiple files):
1. **Install a VS Code Extension**:
- Use extensions like "CodeSnap" or "PrintCode" to capture the code from multiple files.
2. **Script to Combine Files**:
- Write a script to concatenate all your files into one document.
- Example in Python:
```python
import os
directory = './path_to_codebase'
output_file = 'codebase.pdf'
with open('combined_code.txt', 'w') as outfile:
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.py'): # Adjust for your file types
with open(os.path.join(root, file), 'r') as infile:
outfile.write(f"File: {file}\n")
outfile.write(infile.read())
outfile.write("\n\n")
print(f"Combined code written to combined_code.txt")
```
3. **Convert to PDF**:
- Use a tool like `pandoc` to convert the combined `.txt` file to PDF:
```bash
pandoc combined_code.txt -o codebase.pdf
```
---
### **Method 4: Use Online Tools**
1. **Upload Files to Online Converters**:
- Use services like [Online Code to PDF](https://codebeautify.org/code-to-pdf-converter) or similar tools.
- Upload each file and download the PDFs.
2. **Combine PDFs**:
- Use a tool like [PDFsam](https://pdfsam.org/) to merge individual PDFs into a single document.
---
### **Method 5: GitHub and PDF Export**
If your project is hosted on GitHub:
1. Clone or Zip your repository.
2. Use tools like `gitscribe` or GitHub's own markdown-to-PDF converters to generate a report-style document.
---
### Additional Notes:
- For consistent formatting, ensure syntax highlighting is enabled in the exported document.
- If you want to include project structure (folders and files), generate a file tree using:
```bash
tree > file_tree.txt
```
Then append it to your exported document.
Choose the method that best fits your needs depending on the size and scope of your project. Let me know if you'd like detailed steps for any specific method!
gpt-4o